diff --git a/.travis.yml b/.travis.yml index af38252ce..a4c8e91df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,19 +17,27 @@ env: - secure: "YnhS+8n6B+uoyaYfaJ3Lei7cSJqHDPiKJCKFIF2c87YDfmCvAJke8QtE7IzjYDs7UFkTCM4ox+ph2bERUrxZbSCyEkHdjIZpKuMJfYWja/jgMqTMxdyOH9y8JLFbZsSXDIXDwqBlC6vVyl1fP90M35wuWcNTs6tctfVWVofEFbs=" - GITTEST_INVASIVE_FS_SIZE=1 matrix: - - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" - - OPTIONS="-DTHREADSAFE=OFF -DBUILD_EXAMPLES=ON" + - OPTIONS="-DTHREADSAFE=ON -DENABLE_TRACE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_WERROR=ON" + - OPTIONS="-DTHREADSAFE=OFF -DBUILD_EXAMPLES=ON -DENABLE_WERROR=ON" + +dist: trusty +sudo: false addons: - apt: - packages: - - cmake - - libssh2-1-dev - - openssh-client - - openssh-server - - valgrind - -sudo: false + apt: + sources: + - sourceline: 'deb https://dl.bintray.com/libgit2/ci-dependencies trusty libgit2deps' + key_url: 'https://bintray.com/user/downloadSubjectPublicKey?username=bintray' + packages: + cmake + curl + libcurl3 + libcurl3-gnutls + libcurl4-gnutls-dev + libssh2-1-dev + openssh-client + openssh-server + valgrind matrix: fast_finish: true @@ -40,16 +48,18 @@ matrix: - compiler: gcc env: COVERITY=1 os: linux + dist: trusty - compiler: gcc env: - VALGRIND=1 OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" os: linux + dist: trusty allow_failures: - env: COVERITY=1 install: - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi + - if [ -f ./script/install-deps-${TRAVIS_OS_NAME}.sh ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi # Run the Build script and tests script: diff --git a/AUTHORS b/AUTHORS index 61e2113ec..458ff06c3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,6 +22,7 @@ Dmitry Kakurin Dmitry Kovega Emeric Fermas Emmanuel Rodriguez +Eric Myhre Florian Forster Holger Weiss Ingmar Vanhassel diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ecba426..ba0cb4ea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.26 + 1 +v0.27 + 1 --------- ### Changes or improvements @@ -9,6 +9,122 @@ v0.26 + 1 ### Breaking API changes +v0.27 +--------- + +### Changes or improvements + +* Improved `p_unlink` in `posix_w32.c` to try and make a file writable + before sleeping in the retry loop to prevent unnecessary calls to sleep. + +* The CMake build infrastructure has been improved to speed up building time. + +* A new CMake option "-DUSE_HTTPS=" makes it possible to explicitly + choose an HTTP backend. + +* A new CMake option "-DSHA1_BACKEND=" makes it possible to explicitly + choose an SHA1 backend. The collision-detecting backend is now the default. + +* A new CMake option "-DUSE_BUNDLED_ZLIB" makes it possible to explicitly use + the bundled zlib library. + +* A new CMake option "-DENABLE_REPRODUCIBLE_BUILDS" makes it possible to + generate a reproducible static archive. This requires support from your + toolchain. + +* The minimum required CMake version has been bumped to 2.8.11. + +* Writing to a configuration file now preserves the case of the key given by the + caller for the case-insensitive portions of the key (existing sections are + used even if they don't match). + +* We now support conditional includes in configuration files. + +* Fix for handling re-reading of configuration files with includes. + +* Fix for reading patches which contain exact renames only. + +* Fix for reading patches with whitespace in the compared files' paths. + +* We will now fill `FETCH_HEAD` from all passed refspecs instead of overwriting + with the last one. + +* There is a new diff option, `GIT_DIFF_INDENT_HEURISTIC` which activates a + heuristic which takes into account whitespace and indentation in order to + produce better diffs when dealing with ambiguous diff hunks. + +* Fix for pattern-based ignore rules where files ignored by a rule cannot be + un-ignored by another rule. + +* Sockets opened by libgit2 are now being closed on exec(3) if the platform + supports it. + +* Fix for peeling annotated tags from packed-refs files. + +* Fix reading huge loose objects from the object database. + +* Fix files not being treated as modified when only the file mode has changed. + +* We now explicitly reject adding submodules to the index via + `git_index_add_frombuffer`. + +* Fix handling of `GIT_DIFF_FIND_RENAMES_FROM_REWRITES` raising `SIGABRT` when + one file has been deleted and another file has been rewritten. + +* Fix for WinHTTP not properly handling NTLM and Negotiate challenges. + +* When using SSH-based transports, we now repeatedly ask for the passphrase to + decrypt the private key in case a wrong passphrase is being provided. + +* When generating conflict markers, they will now use the same line endings as + the rest of the file. + +### API additions + +* The `git_merge_file_options` structure now contains a new setting, + `marker_size`. This allows users to set the size of markers that + delineate the sides of merged files in the output conflict file. + By default this is 7 (`GIT_MERGE_CONFLICT_MARKER_SIZE`), which + produces output markers like `<<<<<<<` and `>>>>>>>`. + +* `git_remote_create_detached()` creates a remote that is not associated + to any repository (and does not apply configuration like 'insteadof' rules). + This is mostly useful for e.g. emulating `git ls-remote` behavior. + +* `git_diff_patchid()` lets you generate patch IDs for diffs. + +* `git_status_options` now has an additional field `baseline` to allow creating + status lists against different trees. + +* New family of functions to allow creating notes for a specific notes commit + instead of for a notes reference. + +* New family of functions to allow parsing message trailers. This API is still + experimental and may change in future releases. + +### API removals + +### Breaking API changes + +* Signatures now distinguish between +0000 and -0000 UTC offsets. + +* The certificate check callback in the WinHTTP transport will now receive the + `message_cb_payload` instead of the `cred_acquire_payload`. + +* We are now reading symlinked directories under .git/refs. + +* We now refuse creating branches named "HEAD". + +* We now refuse reading and writing all-zero object IDs into the + object database. + +* We now read the effective user's configuration file instead of the real user's + configuration in case libgit2 runs as part of a setuid binary. + +* The `git_odb_open_rstream` function and its `readstream` callback in the + `git_odb_backend` interface have changed their signatures to allow providing + the object's size and type to the caller. + v0.26 ----- diff --git a/CMakeLists.txt b/CMakeLists.txt index 4783e3ef9..2ca5354a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,17 @@ # > cmake --build . --target install PROJECT(libgit2 C) -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) CMAKE_POLICY(SET CMP0015 NEW) +IF (POLICY CMP0051) + CMAKE_POLICY(SET CMP0051 NEW) +ENDIF() +IF (POLICY CMP0042) + CMAKE_POLICY(SET CMP0042 NEW) +ENDIF() # Add find modules to the path -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") +SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${libgit2_SOURCE_DIR}/cmake/Modules/") INCLUDE(CheckLibraryExists) INCLUDE(CheckFunctionExists) @@ -24,6 +30,11 @@ INCLUDE(CheckSymbolExists) INCLUDE(CheckStructHasMember) INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgConfig) +INCLUDE(FindThreads) +INCLUDE(FindStatNsec) +INCLUDE(IdeSplitSources) +INCLUDE(FeatureSummary) +INCLUDE(EnableWarnings) # Build options # @@ -37,22 +48,23 @@ OPTION( PROFILE "Generate profiling information" OFF ) OPTION( ENABLE_TRACE "Enables tracing support" OFF ) OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) -OPTION( USE_SHA1DC "Use SHA-1 with collision detection" OFF ) -OPTION( USE_ICONV "Link with and use iconv library" OFF ) +SET(SHA1_BACKEND "CollisionDetection" CACHE STRING "Backend to use for SHA1. One of Generic, OpenSSL, Win32, CommonCrypto, CollisionDetection. ") OPTION( USE_SSH "Link with libssh to enable SSH support" ON ) +OPTION( USE_HTTPS "Enable HTTPS support. Can be set to a specific backend" ON ) OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF ) OPTION( VALGRIND "Configure build for valgrind" OFF ) OPTION( CURL "Use curl for HTTP if available" ON) +OPTION( USE_EXT_HTTP_PARSER "Use system HTTP_Parser if available" ON) OPTION( DEBUG_POOL "Enable debug pool allocator" OFF ) +OPTION( ENABLE_WERROR "Enable compilation with -Werror" OFF ) +OPTION( USE_BUNDLED_ZLIB "Use the bundled version of zlib" OFF ) -IF(DEBUG_POOL) - ADD_DEFINITIONS(-DGIT_DEBUG_POOL) +IF (UNIX AND NOT APPLE) + OPTION( ENABLE_REPRODUCIBLE_BUILDS "Enable reproducible builds" OFF ) ENDIF() -IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - SET( USE_ICONV ON ) - FIND_PACKAGE(Security) - FIND_PACKAGE(CoreFoundation REQUIRED) +IF (APPLE) + OPTION( USE_ICONV "Link with and use iconv library" ON ) ENDIF() IF(MSVC) @@ -71,10 +83,6 @@ IF(MSVC) # If you want to embed a copy of libssh2 into libgit2, pass a # path to libssh2 OPTION( EMBED_SSH_PATH "Path to libssh2 to embed (Windows)" OFF ) - - ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS) - ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) - ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE) ENDIF() @@ -89,313 +97,21 @@ IF(MSVC) OPTION(MSVC_CRTDBG "Enable CRTDBG memory leak reporting" OFF) ENDIF() -IF (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - OPTION( USE_OPENSSL "Link with and use openssl library" ON ) -ENDIF() - -CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtim "sys/types.h;sys/stat.h" - HAVE_STRUCT_STAT_ST_MTIM LANGUAGE C) -CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtimespec "sys/types.h;sys/stat.h" - HAVE_STRUCT_STAT_ST_MTIMESPEC LANGUAGE C) -CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtime_nsec sys/stat.h - HAVE_STRUCT_STAT_MTIME_NSEC LANGUAGE C) - -IF (HAVE_STRUCT_STAT_ST_MTIM) - CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtim.tv_nsec sys/stat.h - HAVE_STRUCT_STAT_NSEC LANGUAGE C) -ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC) - CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtimespec.tv_nsec sys/stat.h - HAVE_STRUCT_STAT_NSEC LANGUAGE C) -ELSE () - SET( HAVE_STRUCT_STAT_NSEC ON ) -ENDIF() - -IF (HAVE_STRUCT_STAT_NSEC OR WIN32) - OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" ON ) -ENDIF() - -# This variable will contain the libraries we need to put into -# libgit2.pc's Requires.private. That is, what we're linking to or -# what someone who's statically linking us needs to link to. -SET(LIBGIT2_PC_REQUIRES "") -# This will be set later if we use the system's http-parser library or -# use iconv (OSX) and will be written to the Libs.private field in the -# pc file. -SET(LIBGIT2_PC_LIBS "") - -# Installation paths -# -SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.") -SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.") -SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.") - -# Set a couple variables to be substituted inside the .pc file. -# We can't just use LIB_INSTALL_DIR in the .pc file, as passing them as absolue -# or relative paths is both valid and supported by cmake. -SET (PKGCONFIG_PREFIX ${CMAKE_INSTALL_PREFIX}) - -IF(IS_ABSOLUTE ${LIB_INSTALL_DIR}) - SET (PKGCONFIG_LIBDIR ${LIB_INSTALL_DIR}) -ELSE(IS_ABSOLUTE ${LIB_INSTALL_DIR}) - SET (PKGCONFIG_LIBDIR "\${prefix}/${LIB_INSTALL_DIR}") -ENDIF (IS_ABSOLUTE ${LIB_INSTALL_DIR}) - -IF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - SET (PKGCONFIG_INCLUDEDIR ${INCLUDE_INSTALL_DIR}) -ELSE(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - SET (PKGCONFIG_INCLUDEDIR "\${prefix}/${INCLUDE_INSTALL_DIR}") -ENDIF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - -FUNCTION(TARGET_OS_LIBRARIES target) - IF(WIN32) - TARGET_LINK_LIBRARIES(${target} ws2_32) - ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") - TARGET_LINK_LIBRARIES(${target} socket nsl) - LIST(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") - SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE) - ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Haiku") - TARGET_LINK_LIBRARIES(${target} network) - LIST(APPEND LIBGIT2_PC_LIBS "-lnetwork") - SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE) - ENDIF() - CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" NEED_LIBRT) - IF(NEED_LIBRT) - TARGET_LINK_LIBRARIES(${target} rt) - LIST(APPEND LIBGIT2_PC_LIBS "-lrt") - SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE) - ENDIF() - - IF(THREADSAFE) - TARGET_LINK_LIBRARIES(${target} ${CMAKE_THREAD_LIBS_INIT}) - LIST(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) - SET(LIBGIT2_PC_LIBS ${LIBGIT2_PC_LIBS} PARENT_SCOPE) - ENDIF() -ENDFUNCTION() - -# This function splits the sources files up into their appropriate -# subdirectories. This is especially useful for IDEs like Xcode and -# Visual Studio, so that you can navigate into the libgit2_clar project, -# and see the folders within the tests folder (instead of just seeing all -# source and tests in a single folder.) -FUNCTION(IDE_SPLIT_SOURCES target) - IF(MSVC_IDE OR CMAKE_GENERATOR STREQUAL Xcode) - GET_TARGET_PROPERTY(sources ${target} SOURCES) - FOREACH(source ${sources}) - IF(source MATCHES ".*/") - STRING(REPLACE ${CMAKE_CURRENT_SOURCE_DIR}/ "" rel ${source}) - IF(rel) - STRING(REGEX REPLACE "/([^/]*)$" "" rel ${rel}) - IF(rel) - STRING(REPLACE "/" "\\\\" rel ${rel}) - SOURCE_GROUP(${rel} FILES ${source}) - ENDIF() - ENDIF() - ENDIF() - ENDFOREACH() - ENDIF() -ENDFUNCTION() - -FILE(STRINGS "include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$") +FILE(STRINGS "${libgit2_SOURCE_DIR}/include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$") STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${GIT2_HEADER}") STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_MINOR "${GIT2_HEADER}") STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_REV "${GIT2_HEADER}") SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}") -FILE(STRINGS "include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$") +FILE(STRINGS "${libgit2_SOURCE_DIR}/include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$") STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}") -# Find required dependencies -INCLUDE_DIRECTORIES(src include) - -IF (SECURITY_FOUND) - # OS X 10.7 and older do not have some functions we use, fall back to OpenSSL there - CHECK_LIBRARY_EXISTS("${SECURITY_DIRS}" SSLCreateContext "Security/SecureTransport.h" HAVE_NEWER_SECURITY) - IF (HAVE_NEWER_SECURITY) - MESSAGE("-- Found Security ${SECURITY_DIRS}") - LIST(APPEND LIBGIT2_PC_LIBS "-framework Security") - ELSE() - MESSAGE("-- Security framework is too old, falling back to OpenSSL") - SET(SECURITY_FOUND "NO") - SET(SECURITY_DIRS "") - SET(SECURITY_DIR "") - SET(USE_OPENSSL "ON") - ENDIF() -ENDIF() - -IF (COREFOUNDATION_FOUND) - MESSAGE("-- Found CoreFoundation ${COREFOUNDATION_DIRS}") - LIST(APPEND LIBGIT2_PC_LIBS "-framework CoreFoundation") -ENDIF() - - -IF (WIN32 AND EMBED_SSH_PATH) - FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") - INCLUDE_DIRECTORIES("${EMBED_SSH_PATH}/include") - FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") - ADD_DEFINITIONS(-DGIT_SSH) -ENDIF() - -IF (WIN32 AND WINHTTP) - ADD_DEFINITIONS(-DGIT_WINHTTP) - ADD_DEFINITIONS(-DGIT_HTTPS) - - # Since MinGW does not come with headers or an import library for winhttp, - # we have to include a private header and generate our own import library - IF (MINGW) - FIND_PROGRAM(DLLTOOL dlltool CMAKE_FIND_ROOT_PATH_BOTH) - IF (NOT DLLTOOL) - MESSAGE(FATAL_ERROR "Could not find dlltool command") - ENDIF () - - SET(LIBWINHTTP_PATH "${CMAKE_CURRENT_BINARY_DIR}/deps/winhttp") - FILE(MAKE_DIRECTORY ${LIBWINHTTP_PATH}) - - IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - set(WINHTTP_DEF "${CMAKE_CURRENT_SOURCE_DIR}/deps/winhttp/winhttp64.def") - ELSE() - set(WINHTTP_DEF "${CMAKE_CURRENT_SOURCE_DIR}/deps/winhttp/winhttp.def") - ENDIF() - - ADD_CUSTOM_COMMAND( - OUTPUT ${LIBWINHTTP_PATH}/libwinhttp.a - COMMAND ${DLLTOOL} -d ${WINHTTP_DEF} -k -D winhttp.dll -l libwinhttp.a - DEPENDS ${WINHTTP_DEF} - WORKING_DIRECTORY ${LIBWINHTTP_PATH} - ) - - SET_SOURCE_FILES_PROPERTIES( - ${CMAKE_CURRENT_SOURCE_DIR}/src/transports/winhttp.c - PROPERTIES OBJECT_DEPENDS ${LIBWINHTTP_PATH}/libwinhttp.a - ) - - INCLUDE_DIRECTORIES(deps/winhttp) - LINK_DIRECTORIES(${LIBWINHTTP_PATH}) - ENDIF () - - LINK_LIBRARIES(winhttp rpcrt4 crypt32 ole32) - LIST(APPEND LIBGIT2_PC_LIBS "-lwinhttp" "-lrpcrt4" "-lcrypt32" "-lole32") -ELSE () - IF (CURL) - PKG_CHECK_MODULES(CURL libcurl) - ENDIF () - - IF (NOT AMIGA AND USE_OPENSSL) - FIND_PACKAGE(OpenSSL) - ENDIF () - - IF (CURL_FOUND) - ADD_DEFINITIONS(-DGIT_CURL) - INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIRS}) - LINK_DIRECTORIES(${CURL_LIBRARY_DIRS}) - LINK_LIBRARIES(${CURL_LIBRARIES}) - LIST(APPEND LIBGIT2_PC_LIBS ${CURL_LDFLAGS}) - ENDIF() -ENDIF() - -# Specify sha1 implementation -IF (USE_SHA1DC) - ADD_DEFINITIONS(-DGIT_SHA1_COLLISIONDETECT) - ADD_DEFINITIONS(-DSHA1DC_NO_STANDARD_INCLUDES=1) - ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\") - ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\") - FILE(GLOB SRC_SHA1 src/hash/hash_collisiondetect.c src/hash/sha1dc/*) -ELSEIF (WIN32 AND NOT MINGW) - ADD_DEFINITIONS(-DGIT_SHA1_WIN32) - FILE(GLOB SRC_SHA1 src/hash/hash_win32.c) -ELSEIF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - ADD_DEFINITIONS(-DGIT_SHA1_COMMON_CRYPTO) -ELSEIF (OPENSSL_FOUND) - ADD_DEFINITIONS(-DGIT_SHA1_OPENSSL) - IF (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - LIST(APPEND LIBGIT2_PC_LIBS "-lssl") - ELSE() - SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} openssl") - ENDIF () -ELSE() - FILE(GLOB SRC_SHA1 src/hash/hash_generic.c) -ENDIF() - -# Enable tracing -IF (ENABLE_TRACE STREQUAL "ON") - ADD_DEFINITIONS(-DGIT_TRACE) -ENDIF() - -# Include POSIX regex when it is required -IF(WIN32 OR AMIGA OR CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") - INCLUDE_DIRECTORIES(deps/regex) - SET(SRC_REGEX deps/regex/regex.c) -ENDIF() - -# Optional external dependency: http-parser -FIND_PACKAGE(HTTP_Parser) -IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) - INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS}) - LINK_LIBRARIES(${HTTP_PARSER_LIBRARIES}) - LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") -ELSE() - MESSAGE(STATUS "http-parser version 2 was not found; using bundled 3rd-party sources.") - INCLUDE_DIRECTORIES(deps/http-parser) - FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h) -ENDIF() - -# Optional external dependency: zlib -FIND_PACKAGE(ZLIB) -IF (ZLIB_FOUND) - INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) - LINK_LIBRARIES(${ZLIB_LIBRARIES}) - IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - LIST(APPEND LIBGIT2_PC_LIBS "-lz") - ELSE() - SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} zlib") - ENDIF() -ELSE() - MESSAGE(STATUS "zlib was not found; using bundled 3rd-party sources." ) - INCLUDE_DIRECTORIES(deps/zlib) - ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) - FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h) -ENDIF() - -# Optional external dependency: libssh2 -IF (USE_SSH) - PKG_CHECK_MODULES(LIBSSH2 libssh2) -ENDIF() -IF (LIBSSH2_FOUND) - ADD_DEFINITIONS(-DGIT_SSH) - INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIRS}) - LINK_DIRECTORIES(${LIBSSH2_LIBRARY_DIRS}) - LIST(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS}) - #SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} ${LIBSSH2_LDFLAGS}") - SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) - - CHECK_LIBRARY_EXISTS("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS) - IF (HAVE_LIBSSH2_MEMORY_CREDENTIALS) - ADD_DEFINITIONS(-DGIT_SSH_MEMORY_CREDENTIALS) - ENDIF() -ELSE() - MESSAGE(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") -ENDIF() - -# Optional external dependency: libgssapi -IF (USE_GSSAPI) - FIND_PACKAGE(GSSAPI) -ENDIF() -IF (GSSAPI_FOUND) - ADD_DEFINITIONS(-DGIT_GSSAPI) -ENDIF() - -# Optional external dependency: iconv -IF (USE_ICONV) - FIND_PACKAGE(Iconv) -ENDIF() -IF (ICONV_FOUND) - ADD_DEFINITIONS(-DGIT_USE_ICONV) - INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR}) - LIST(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) -ENDIF() - # Platform specific compilation flags IF (MSVC) + ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS) + ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) + ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE) STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") @@ -417,8 +133,9 @@ IF (MSVC) ENDIF() IF (MSVC_CRTDBG) - SET(CRT_FLAG_DEBUG "${CRT_FLAG_DEBUG} /DGIT_MSVC_CRTDBG") - SET(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES}" "Dbghelp.lib") + SET(GIT_MSVC_CRTDBG 1) + SET(CRT_FLAG_DEBUG "${CRT_FLAG_DEBUG}") + SET(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} Dbghelp.lib") ENDIF() # /Zi - Create debugging information @@ -466,13 +183,17 @@ IF (MSVC) SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") SET(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}") - - SET(WIN_RC "src/win32/git2.rc") ELSE () + IF (ENABLE_REPRODUCIBLE_BUILDS) + SET(CMAKE_C_ARCHIVE_CREATE " Dqc ") + SET(CMAKE_C_ARCHIVE_APPEND " Dq ") + SET(CMAKE_C_ARCHIVE_FINISH " -D ") + ENDIF() + SET(CMAKE_C_FLAGS "-D_GNU_SOURCE ${CMAKE_C_FLAGS}") - ADD_C_FLAG_IF_SUPPORTED(-Wall) - ADD_C_FLAG_IF_SUPPORTED(-Wextra) + ENABLE_WARNINGS(all) + ENABLE_WARNINGS(extra) IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") SET(CMAKE_C_FLAGS "-std=c99 -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__ -D_POSIX_PTHREAD_SEMANTICS ${CMAKE_C_FLAGS}") @@ -495,16 +216,17 @@ ELSE () ADD_DEFINITIONS(-D__USE_MINGW_ANSI_STDIO=1) ENDIF () - ADD_C_FLAG_IF_SUPPORTED(-Wdocumentation) - ADD_C_FLAG_IF_SUPPORTED(-Wno-missing-field-initializers) - ADD_C_FLAG_IF_SUPPORTED(-Wstrict-aliasing=2) - ADD_C_FLAG_IF_SUPPORTED(-Wstrict-prototypes) - ADD_C_FLAG_IF_SUPPORTED(-Wdeclaration-after-statement) - ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-const-variable) - ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-function) + ENABLE_WARNINGS(documentation) + DISABLE_WARNINGS(missing-field-initializers) + ENABLE_WARNINGS(strict-aliasing) + ENABLE_WARNINGS(strict-prototypes) + ENABLE_WARNINGS(declaration-after-statement) + ENABLE_WARNINGS(shift-count-overflow) + DISABLE_WARNINGS(unused-const-variable) + DISABLE_WARNINGS(unused-function) IF (APPLE) # Apple deprecated OpenSSL - ADD_C_FLAG_IF_SUPPORTED(-Wno-deprecated-declarations) + DISABLE_WARNINGS(deprecated-declarations) ENDIF() IF (PROFILE) @@ -513,26 +235,6 @@ ELSE () ENDIF () ENDIF() -CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) -IF (HAVE_REGCOMP_L) - ADD_DEFINITIONS(-DHAVE_REGCOMP_L) -ENDIF () - -CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) -IF (HAVE_FUTIMENS) - ADD_DEFINITIONS(-DHAVE_FUTIMENS) -ENDIF () - -CHECK_FUNCTION_EXISTS(qsort_r HAVE_QSORT_R) -IF (HAVE_QSORT_R) - ADD_DEFINITIONS(-DHAVE_QSORT_R) -ENDIF () - -CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S) -IF (HAVE_QSORT_S) - ADD_DEFINITIONS(-DHAVE_QSORT_S) -ENDIF () - IF( NOT CMAKE_CONFIGURATION_TYPES ) # Build Debug by default IF (NOT CMAKE_BUILD_TYPE) @@ -543,177 +245,18 @@ ELSE() # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE ENDIF() -IF (SECURITY_FOUND) - ADD_DEFINITIONS(-DGIT_SECURE_TRANSPORT) - ADD_DEFINITIONS(-DGIT_HTTPS) - INCLUDE_DIRECTORIES(${SECURITY_INCLUDE_DIR}) -ENDIF () - -IF (OPENSSL_FOUND) - ADD_DEFINITIONS(-DGIT_OPENSSL) - ADD_DEFINITIONS(-DGIT_HTTPS) - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) - SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) -ENDIF() - - - -IF (THREADSAFE) - IF (NOT WIN32) - FIND_PACKAGE(Threads REQUIRED) - ENDIF() - - ADD_DEFINITIONS(-DGIT_THREADS) -ENDIF() - -IF (USE_NSEC) - ADD_DEFINITIONS(-DGIT_USE_NSEC) -ENDIF() - -IF (HAVE_STRUCT_STAT_ST_MTIM) - ADD_DEFINITIONS(-DGIT_USE_STAT_MTIM) -ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC) - ADD_DEFINITIONS(-DGIT_USE_STAT_MTIMESPEC) -ELSEIF (HAVE_STRUCT_STAT_ST_MTIME_NSEC) - ADD_DEFINITIONS(-DGIT_USE_STAT_MTIME_NSEC) -ENDIF() - -ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) - -# Collect sourcefiles -FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h) - -# On Windows use specific platform sources -IF (WIN32 AND NOT CYGWIN) - ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) - FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h) -ELSEIF (AMIGA) - ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) -ELSE() - IF (VALGRIND) - ADD_DEFINITIONS(-DNO_MMAP) - ENDIF() - FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h) -ENDIF() -FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h) - -# Determine architecture of the machine -IF (CMAKE_SIZEOF_VOID_P EQUAL 8) - ADD_DEFINITIONS(-DGIT_ARCH_64) -ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4) - ADD_DEFINITIONS(-DGIT_ARCH_32) -ELSEIF (CMAKE_SIZEOF_VOID_P) - MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") -ELSE() - MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") -ENDIF() - -# Compile and link libgit2 -ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1} ${WIN_RC}) -TARGET_LINK_LIBRARIES(git2 ${SECURITY_DIRS}) -TARGET_LINK_LIBRARIES(git2 ${COREFOUNDATION_DIRS}) -TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) -TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES}) -TARGET_LINK_LIBRARIES(git2 ${GSSAPI_LIBRARIES}) -TARGET_LINK_LIBRARIES(git2 ${ICONV_LIBRARIES}) -TARGET_OS_LIBRARIES(git2) - -# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) -# Win64+MSVC+static libs = linker error -IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) - SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") -ENDIF() - -IDE_SPLIT_SOURCES(git2) - -IF (SONAME) - SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) - SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION}) - IF (LIBGIT2_FILENAME) - ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") - SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) - ELSEIF (DEFINED LIBGIT2_PREFIX) - SET_TARGET_PROPERTIES(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") - ENDIF() -ENDIF() -STRING(REPLACE ";" " " LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS}") -CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) - -IF (MSVC_IDE) - # Precompiled headers - SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") -ENDIF () - -# Install -INSTALL(TARGETS git2 - RUNTIME DESTINATION ${BIN_INSTALL_DIR} - LIBRARY DESTINATION ${LIB_INSTALL_DIR} - ARCHIVE DESTINATION ${LIB_INSTALL_DIR} -) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) -INSTALL(DIRECTORY include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} ) -INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) +ADD_SUBDIRECTORY(src) # Tests +IF (NOT MSVC) + IF (NOT BUILD_SHARED_LIBS) + SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + ENDIF() +ENDIF () + IF (BUILD_CLAR) - FIND_PACKAGE(PythonInterp) - - IF(NOT PYTHONINTERP_FOUND) - MESSAGE(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. " - "Make sure python is available, or pass -DBUILD_CLAR=OFF to skip building the tests") - ENDIF() - - SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/") - SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests") - SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.") - ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") - ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") - ADD_DEFINITIONS(-DCLAR_TMPDIR=\"libgit2_tests\") - - INCLUDE_DIRECTORIES(${CLAR_PATH}) - FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) - SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar_libgit2_trace.c" "${CLAR_PATH}/clar_libgit2_timer.c" "${CLAR_PATH}/clar.c") - - ADD_CUSTOM_COMMAND( - OUTPUT ${CLAR_PATH}/clar.suite - COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress . - DEPENDS ${SRC_TEST} - WORKING_DIRECTORY ${CLAR_PATH} - ) - - SET_SOURCE_FILES_PROPERTIES( - ${CLAR_PATH}/clar.c - PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) - - ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1}) - - TARGET_LINK_LIBRARIES(libgit2_clar ${COREFOUNDATION_DIRS}) - TARGET_LINK_LIBRARIES(libgit2_clar ${SECURITY_DIRS}) - TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) - TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES}) - TARGET_LINK_LIBRARIES(libgit2_clar ${GSSAPI_LIBRARIES}) - TARGET_LINK_LIBRARIES(libgit2_clar ${ICONV_LIBRARIES}) - TARGET_OS_LIBRARIES(libgit2_clar) - IDE_SPLIT_SOURCES(libgit2_clar) - - IF (MSVC_IDE) - # Precompiled headers - SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - ENDIF () - ENABLE_TESTING() - IF (WINHTTP OR OPENSSL_FOUND OR SECURITY_FOUND) - ADD_TEST(libgit2_clar libgit2_clar -ionline -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) - ELSE () - ADD_TEST(libgit2_clar libgit2_clar -v -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) - ENDIF () - - # Add a test target which runs the cred callback tests, to be - # called after setting the url and user - ADD_TEST(libgit2_clar-cred_callback libgit2_clar -v -sonline::clone::cred_callback) - ADD_TEST(libgit2_clar-proxy_credentials_in_url libgit2_clar -v -sonline::clone::proxy_credentials_in_url) - ADD_TEST(libgit2_clar-proxy_credentials_request libgit2_clar -v -sonline::clone::proxy_credentials_request) + ADD_SUBDIRECTORY(tests) ENDIF () IF (TAGS) @@ -738,3 +281,11 @@ ENDIF () IF (BUILD_EXAMPLES) ADD_SUBDIRECTORY(examples) ENDIF () + +IF(CMAKE_VERSION VERSION_GREATER 3) + FEATURE_SUMMARY(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") + FEATURE_SUMMARY(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") +ELSE() + PRINT_ENABLED_FEATURES() + PRINT_DISABLED_FEATURES() +ENDIF() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a143570..7f372233a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,22 @@ if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change merged in. +In addition to outlining your thought process in the PR's description, please +also try to document it in your commits. We welcome it if every commit has a +description of why you have been doing your changes alongside with your +reasoning why this is a good idea. The messages shall be written in +present-tense and in an imperative style (e.g. "Add feature foobar", not "Added +feature foobar" or "Adding feature foobar"). Lines should be wrapped at 80 +characters so people with small screens are able to read the commit messages in +their terminal without any problem. + +To make it easier to attribute commits to certain parts of our code base, we +also prefer to have the commit subject be prefixed with a "scope". E.g. if you +are changing code in our merging subsystem, make sure to prefix the subject with +"merge:". The first word following the colon shall start with an lowercase +letter. The maximum line length for the subject is 70 characters, preferably +shorter. + If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's not ready to merge). These early PRs are welcome and will help in getting diff --git a/PROJECTS.md b/PROJECTS.md index f53a2e120..419fdcdfc 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -36,10 +36,6 @@ These are good small projects to get started with libgit2. trick to this one will be doing it in a manner that is clean and simple, but still handles the various cases correctly (e.g. `-B/70%` is apparently a legal setting). - * Implement the `--log-size` option for `examples/log.c`. I think all - the data is available, you would just need to add the code into the - `print_commit()` routine (along with a way of passing the option - into that function). * As an extension to the matching idea for `examples/log.c`, add the `-i` option to use `strcasestr()` for matches. * For `examples/log.c`, implement the `--first-parent` option now that diff --git a/README.md b/README.md index 1bbd37134..9fe99d8d5 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,20 @@ libgit2 - the Git linkable library [![Coverity Scan Build Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) `libgit2` is a portable, pure C implementation of the Git core methods -provided as a re-entrant linkable library with a solid API, allowing you to -write native speed custom Git applications in any language with bindings. +provided as a linkable library with a solid API, allowing to build Git +functionality into your application. Language bindings like +[Rugged](https://github.com/libgit2/rugged) (Ruby), +[LibGit2Sharp](https://github.com/libgit2/libgit2sharp) (.NET), +[pygit2](http://www.pygit2.org/) (Python) and +[NodeGit](http://nodegit.org) (Node) allow you to build Git tooling +in your favorite language. + +`libgit2` is used to power Git GUI clients like +[GitKraken](https://gitkraken.com/) and [gmaster](https://gmaster.io/) +and on Git hosting providers like [GitHub](https://github.com/), +[GitLab](https://gitlab.com/) and +[Visual Studio Team Services](https://visualstudio.com/team-services/). +We perform the merge every time you click "merge pull request". `libgit2` is licensed under a **very permissive license** (GPLv2 with a special Linking Exception). This basically means that you can link it (unmodified) @@ -15,6 +27,30 @@ with any kind of software without having to release its source code. Additionally, the example code has been released to the public domain (see the [separate license](examples/COPYING) for more information). +Quick Start +=========== + +**Prerequisites** for building libgit2: + +1. [CMake](https://cmake.org/), and is recommended to be installed into + your `PATH`. +2. [Python](https://www.python.org) is used by our test framework, and + should be installed into your `PATH`. +3. C compiler: libgit2 is C90 and should compile on most compilers. + * Windows: Visual Studio is recommended + * Mac: Xcode is recommended + * Unix: gcc or clang is recommended. + +**Build** + +1. Create a build directory beneath the libgit2 source directory, and change + into it: `mkdir build && cd build` +2. Create the cmake build environment: `cmake ..` +3. Build libgit2: `cmake --build .` + +Trouble with these steps? Read `TROUBLESHOOTING.md`. More detailed build +guidance is available below. + Getting Help ============ @@ -40,6 +76,12 @@ on a specific repository, please provide a link to it if possible. We ask that you not open a GitHub Issue for help, only for bug reports. +**Reporting Security Issues** + +In case you think to have found a security issue with libgit2, please do not +open a public issue. Instead, you can report the issue to the private mailing +list [security@libgit2.com](mailto:security@libgit2.com). + What It Can Do ============== @@ -118,6 +160,9 @@ and internal API/coding conventions we use. Building libgit2 - Using CMake ============================== +Building +-------- + `libgit2` builds cleanly on most platforms without any external dependencies. Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available; they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API @@ -133,19 +178,49 @@ On most systems you can build the library using the following commands Alternatively you can point the CMake GUI tool to the CMakeLists.txt file and generate platform specific build project or IDE workspace. +Running Tests +------------- + Once built, you can run the tests from the `build` directory with the command - $ make test + $ ctest -V Alternatively you can run the test suite directly using, $ ./libgit2_clar +Invoking the test suite directly is useful because it allows you to execute +individual tests, or groups of tests using the `-s` flag. For example, to +run the index tests: + + $ ./libgit2_clar -sindex + +To run a single test named `index::racy::diff`, which corresponds to the test +function (`test_index_racy__diff`)[https://github.com/libgit2/libgit2/blob/master/tests/index/racy.c#L23]: + + $ ./libgit2_clar -sindex::racy::diff + +The test suite will print a `.` for every passing test, and an `F` for any +failing test. An `S` indicates that a test was skipped because it is not +applicable to your platform or is particularly expensive. + +**Note:** There should be _no_ failing tests when you build an unmodified +source tree from a [release](https://github.com/libgit2/libgit2/releases), +or from the [master branch](https://github.com/libgit2/libgit2/tree/master). +Please contact us or [open an issue](https://github.com/libgit2/libgit2/issues) +if you see test failures. + +Installation +------------ + To install the library you can specify the install prefix by setting: $ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix $ cmake --build . --target install +Advanced Usage +-------------- + For more advanced use or questions about CMake please read . The following CMake variables are declared: @@ -177,16 +252,6 @@ If you want to build a universal binary for Mac OS X, CMake sets it all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` when configuring. -Windows -------- - -You need to run the CMake commands from the Visual Studio command -prompt, not the regular or Windows SDK one. Select the right generator -for your version with the `-G "Visual Studio X" option. - -See [the website](http://libgit2.github.com/docs/guides/build-and-link/) -for more detailed instructions. - Android ------- @@ -228,6 +293,8 @@ Here are the bindings to libgit2 that are currently available: * git2go * GObject * libgit2-glib +* Guile + * Guile-Git * Haskell * hgit2 * Java diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 000000000..085fff831 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,13 @@ +Troubleshooting libgit2 Problems +================================ + +CMake Failures +-------------- + +* **`Asked for OpenSSL TLS backend, but it wasn't found`** + CMake cannot find your SSL/TLS libraries. By default, libgit2 always + builds with HTTPS support, and you are encouraged to install the + OpenSSL libraries for your system (eg, `apt-get install libssl-dev`). + + For development, if you simply want to disable HTTPS support entirely, + pass the `-DUSE_HTTPS=OFF` argument to `cmake` when configuring it. diff --git a/appveyor.yml b/appveyor.yml index fb3fff7dd..9b14a9c81 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,13 +9,23 @@ environment: GITTEST_INVASIVE_FS_SIZE: 1 matrix: - - GENERATOR: "Visual Studio 11" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + GENERATOR: "Visual Studio 10 2010" ARCH: 32 - - GENERATOR: "Visual Studio 11 Win64" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + GENERATOR: "Visual Studio 10 2010 Win64" ARCH: 64 - - GENERATOR: "MSYS Makefiles" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GENERATOR: "Visual Studio 14 2015" + ARCH: 32 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GENERATOR: "Visual Studio 14 2015 Win64" + ARCH: 64 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GENERATOR: "MSYS Makefiles" ARCH: i686 # this is for 32-bit MinGW-w64 - - GENERATOR: "MSYS Makefiles" + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GENERATOR: "MSYS Makefiles" ARCH: 64 cache: - i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z @@ -26,7 +36,7 @@ build_script: mkdir build cd build if ($env:GENERATOR -ne "MSYS Makefiles") { - cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D MSVC_CRTDBG=ON .. -G"$env:GENERATOR" + cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D BUILD_EXAMPLES=ON -D MSVC_CRTDBG=ON .. -G"$env:GENERATOR" cmake --build . --config Debug } - cmd: | @@ -38,13 +48,8 @@ test_script: # Run this early so we know it's ready by the time we need it $proxyJob = Start-Job { java -jar $Env:APPVEYOR_BUILD_FOLDER\build\poxyproxy.jar -d --port 8080 --credentials foo:bar } ctest -V -R libgit2_clar - $env:GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" - $env:GITTEST_REMOTE_USER="libgit2test" - ctest -V -R libgit2_clar-cred_callback Receive-Job -Job $proxyJob - $env:GITTEST_REMOTE_PROXY_URL = "http://foo:bar@localhost:8080" - ctest -V -R libgit2_clar-proxy_credentials_in_url - $env:GITTEST_REMOTE_PROXY_URL = "http://localhost:8080" + $env:GITTEST_REMOTE_PROXY_URL = "localhost:8080" $env:GITTEST_REMOTE_PROXY_USER = "foo" $env:GITTEST_REMOTE_PROXY_PASS = "bar" - ctest -V -R libgit2_clar-proxy_credentials_request + ctest -V -R libgit2_clar-proxy_credentials diff --git a/cmake/Modules/EnableWarnings.cmake b/cmake/Modules/EnableWarnings.cmake new file mode 100644 index 000000000..e7d7d3986 --- /dev/null +++ b/cmake/Modules/EnableWarnings.cmake @@ -0,0 +1,14 @@ +MACRO(ENABLE_WARNINGS flag) + IF(ENABLE_WERROR) + ADD_C_FLAG_IF_SUPPORTED(-Werror=${flag}) + ELSE() + ADD_C_FLAG_IF_SUPPORTED(-W${flag}) + ENDIF() +ENDMACRO() + +MACRO(DISABLE_WARNINGS flag) + ADD_C_FLAG_IF_SUPPORTED(-Wno-${flag}) + IF(ENABLE_WERROR) + ADD_C_FLAG_IF_SUPPORTED(-Wno-error=${flag}) + ENDIF() +ENDMACRO() diff --git a/cmake/Modules/FindCoreFoundation.cmake b/cmake/Modules/FindCoreFoundation.cmake index ebd619a53..e86ccbf03 100644 --- a/cmake/Modules/FindCoreFoundation.cmake +++ b/cmake/Modules/FindCoreFoundation.cmake @@ -1,9 +1,26 @@ -IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS) - SET(COREFOUNDATION_FOUND TRUE) -ELSE () - FIND_PATH(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h) - FIND_LIBRARY(COREFOUNDATION_DIRS NAMES CoreFoundation) - IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_DIRS) - SET(COREFOUNDATION_FOUND TRUE) - ENDIF () +# Find CoreFoundation.framework +# This will define : +# +# COREFOUNDATION_FOUND +# COREFOUNDATION_LIBRARIES +# COREFOUNDATION_LDFLAGS +# + +FIND_PATH(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h) +FIND_LIBRARY(COREFOUNDATION_LIBRARIES NAMES CoreFoundation) +IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_LIBRARIES) + IF (NOT CoreFoundation_FIND_QUIETLY) + MESSAGE("-- Found CoreFoundation ${COREFOUNDATION_LIBRARIES}") + ENDIF() + SET(COREFOUNDATION_FOUND TRUE) + SET(COREFOUNDATION_LDFLAGS "-framework CoreFoundation") ENDIF () + +IF (CoreFoundation_FIND_REQUIRED AND NOT COREFOUNDATION_FOUND) + MESSAGE(FATAL "-- CoreFoundation not found") +ENDIF() + +MARK_AS_ADVANCED( + COREFOUNDATION_INCLUDE_DIR + COREFOUNDATION_LIBRARIES +) diff --git a/cmake/Modules/FindSecurity.cmake b/cmake/Modules/FindSecurity.cmake index 0decdde92..487f7e500 100644 --- a/cmake/Modules/FindSecurity.cmake +++ b/cmake/Modules/FindSecurity.cmake @@ -1,9 +1,28 @@ -IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS) - SET(SECURITY_FOUND TRUE) -ELSE () - FIND_PATH(SECURITY_INCLUDE_DIR NAMES Security/Security.h) - FIND_LIBRARY(SECURITY_DIRS NAMES Security) - IF (SECURITY_INCLUDE_DIR AND SECURITY_DIRS) - SET(SECURITY_FOUND TRUE) - ENDIF () +# Find Security.framework +# This will define : +# +# SECURITY_FOUND +# SECURITY_LIBRARIES +# SECURITY_LDFLAGS +# SECURITY_HAS_SSLCREATECONTEXT +# + +FIND_PATH(SECURITY_INCLUDE_DIR NAMES Security/Security.h) +FIND_LIBRARY(SECURITY_LIBRARIES NAMES Security) +IF (SECURITY_INCLUDE_DIR AND SECURITY_LIBRARIES) + IF (NOT Security_FIND_QUIETLY) + MESSAGE("-- Found Security ${SECURITY_LIBRARIES}") + ENDIF() + SET(SECURITY_FOUND TRUE) + SET(SECURITY_LDFLAGS "-framework Security") + CHECK_LIBRARY_EXISTS("${SECURITY_LIBRARIES}" SSLCreateContext "Security/SecureTransport.h" SECURITY_HAS_SSLCREATECONTEXT) ENDIF () + +IF (Security_FIND_REQUIRED AND NOT SECURITY_FOUND) + MESSAGE(FATAL "-- Security not found") +ENDIF() + +MARK_AS_ADVANCED( + SECURITY_INCLUDE_DIR + SECURITY_LIBRARIES +) diff --git a/cmake/Modules/FindStatNsec.cmake b/cmake/Modules/FindStatNsec.cmake new file mode 100644 index 000000000..fa550a214 --- /dev/null +++ b/cmake/Modules/FindStatNsec.cmake @@ -0,0 +1,20 @@ +CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtim "sys/types.h;sys/stat.h" + HAVE_STRUCT_STAT_ST_MTIM LANGUAGE C) +CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtimespec "sys/types.h;sys/stat.h" + HAVE_STRUCT_STAT_ST_MTIMESPEC LANGUAGE C) +CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtime_nsec sys/stat.h + HAVE_STRUCT_STAT_MTIME_NSEC LANGUAGE C) + +IF (HAVE_STRUCT_STAT_ST_MTIM) + CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtim.tv_nsec sys/stat.h + HAVE_STRUCT_STAT_NSEC LANGUAGE C) +ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC) + CHECK_STRUCT_HAS_MEMBER("struct stat" st_mtimespec.tv_nsec sys/stat.h + HAVE_STRUCT_STAT_NSEC LANGUAGE C) +ELSE () + SET( HAVE_STRUCT_STAT_NSEC ON ) +ENDIF() + +IF (HAVE_STRUCT_STAT_NSEC OR WIN32) + OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" ON ) +ENDIF() diff --git a/cmake/Modules/IdeSplitSources.cmake b/cmake/Modules/IdeSplitSources.cmake new file mode 100644 index 000000000..e2e09b4ce --- /dev/null +++ b/cmake/Modules/IdeSplitSources.cmake @@ -0,0 +1,22 @@ +# This function splits the sources files up into their appropriate +# subdirectories. This is especially useful for IDEs like Xcode and +# Visual Studio, so that you can navigate into the libgit2_clar project, +# and see the folders within the tests folder (instead of just seeing all +# source and tests in a single folder.) +FUNCTION(IDE_SPLIT_SOURCES target) + IF(MSVC_IDE OR CMAKE_GENERATOR STREQUAL Xcode) + GET_TARGET_PROPERTY(sources ${target} SOURCES) + FOREACH(source ${sources}) + IF(source MATCHES ".*/") + STRING(REPLACE ${libgit2_SOURCE_DIR}/ "" rel ${source}) + IF(rel) + STRING(REGEX REPLACE "/([^/]*)$" "" rel ${rel}) + IF(rel) + STRING(REPLACE "/" "\\\\" rel ${rel}) + SOURCE_GROUP(${rel} FILES ${source}) + ENDIF() + ENDIF() + ENDIF() + ENDFOREACH() + ENDIF() +ENDFUNCTION() diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..42f032b12 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,81 @@ +# Releasing the library + +We have three kinds of releases: "full" releases, maintenance releases and security releases. Full ones release the state of the `master` branch whereas maintenance releases provide bugfixes building on top of the currently released series. Security releases are also for the current series but only contain security fixes on top of the previous release. + +## Full release + +We aim to release once every six months. We start the process by opening an issue. This is accompanied with a feature freeze. From now until the release, only bug fixes are to be merged. Use the following as a base for the issue + + Release v0.X + + Let's release v0.X, codenamed: + + - [ ] Bump the versions in the headers + - [ ] Make a release candidate + - [ ] Plug any final leaks + - [ ] Fix any last-minute issues + - [ ] Make sure CHANGELOG reflects everything worth discussing + - [ ] Update the version in CHANGELOG and the header + - [ ] Produce a release candidate + - [ ] Tag + - [ ] Create maint/v0.X + - [ ] Update any bindings the core team works with + +We tag at least one release candidate. This RC must carry the new version in the headers, including the SOVERSION. If there are no significant issues found, we can go straight to the release after a single RC. This is up to the discretion of the release manager. There is no set time to have the candidate out, but we should we should give downstream projects at least a week to give feedback. + +Preparing the first release candidate includes updating the version number of libgit2 to the new version number. To do so, a pull request shall be submitted that adjusts the version number in the following places: + +- CHANGELOG.md +- include/git2/version.h + +As soon as the pull request is merged, the merge commit shall be tagged with a lightweight tag. + +The tagging happens via GitHub's "releases" tab which lets us attach release notes to a particular tag. In the description we include the changes in `CHANGELOG.md` between the last full release. Use the following as a base for the release notes + + This is the first release of the v0.X series, . The changelog follows. + +followed by the three sections in the changelog. For release candidates we can avoid copying the full changelog and only include any new entries. + +During the freeze, and certainly after the first release candidate, any bindings the core team work with should be updated in order to discover any issues that might come up with the multitude of approaches to memory management, embedding or linking. + +Create a branch `maint/v0.X` at the current state of `master` after you've created the tag. This will be used for maintenance releases and lets our dependents track the latest state of the series. + +## Maintenance release + +Every once in a while, when we feel we've accumulated a significant amount of backportable fixes in the mainline branch, we produce a maintenance release in order to provide fixes or improvements for those who track the releases. This also lets our users and integrators receive updates without having to upgrade to the next full release. + +As a rule of thumb, it's a good idea to produce a maintenance release for the current series when we're getting ready for a full release. This gives the (still) current series a last round of fixes without having to upgrade (which with us potentially means adjusting to API changes). + +Start by opening an issue. Use the following as a base. + + Release v0.X.Y + + Enough fixes have accumulated, let's release v0.X.Y + + - [ ] Select the changes we want to backport + - [ ] Update maint/v0.X + - [ ] Tag + +The list of changes to backport does not need to be comprehensive and we might not backport something if the code in mainline has diverged significantly. These fixes do not include those which require API or ABI changes as we release under the same SOVERSION. + +Do not merge into the `maint/v0.X` until we are getting ready to produce a new release. There is always the possibility that we will need to produce a security release and those must only include the relevant security fixes and not arbitrary fixes we were planning on releasing at some point. + +Here we do not use release candidates as the changes are supposed to be small and proven. + +## Security releases + +This is the same as a maintenance release, except that the fix itself will most likely be developed in a private repository and will only be visible to a select group of people until the release. + +Everything else remains the same. Occasionally we might opt to backport a security fix to the previous series, based on how recently we started the new series and how serious the issue is. + +## Updating documentation + +We use docurium to generate our documentation. It is a tool written in ruby which leverages libclang's documentation parser. Install docurium + + gem install docurium + +and run it against our description file with the tip of master checked out. + + cm doc api.docurium + +It will start up a few proceses and write out the results as a new commit onto the `gh-pages` branch. That can be pushed to GitHub to update what will show up on our documentation reference site. diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 0e491598a..000000000 --- a/examples/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -general -showindex -diff -rev-list -blame -cat-file -init -log -rev-parse -remote -status -tag -for-each-ref -describe -*.dSYM diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 596be45ed..99e2ba9c1 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,7 @@ -FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h) +LINK_DIRECTORIES(${LIBGIT2_LIBDIRS}) +INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES}) + +FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h common.?) ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2}) IF(WIN32 OR ANDROID) TARGET_LINK_LIBRARIES(cgit2 git2) diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index bd7e92dc9..000000000 --- a/examples/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -.PHONY: all - -CC = gcc -CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers -LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status log rev-parse init blame tag remote -APPS += for-each-ref -APPS += describe - -all: $(APPS) - -% : %.c - $(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS) - -clean: - $(RM) $(APPS) - $(RM) -r *.dSYM diff --git a/examples/common.c b/examples/common.c index 96f5eaa8e..118072044 100644 --- a/examples/common.c +++ b/examples/common.c @@ -235,3 +235,13 @@ void treeish_to_tree( git_object_free(obj); } +void *xrealloc(void *oldp, size_t newsz) +{ + void *p = realloc(oldp, newsz); + if (p == NULL) { + fprintf(stderr, "Cannot allocate memory, exiting.\n"); + exit(1); + } + return p; +} + diff --git a/examples/common.h b/examples/common.h index adea0d318..af42324c2 100644 --- a/examples/common.h +++ b/examples/common.h @@ -103,3 +103,8 @@ extern int diff_output( */ extern void treeish_to_tree( git_tree **out, git_repository *repo, const char *treeish); + +/** + * A realloc that exits on failure + */ +extern void *xrealloc(void *oldp, size_t newsz); diff --git a/examples/describe.c b/examples/describe.c index 4cdf61f75..2005de4ae 100644 --- a/examples/describe.c +++ b/examples/describe.c @@ -47,16 +47,6 @@ typedef struct { typedef struct args_info args_info; -static void *xrealloc(void *oldp, size_t newsz) -{ - void *p = realloc(oldp, newsz); - if (p == NULL) { - fprintf(stderr, "Cannot allocate memory, exiting.\n"); - exit(1); - } - return p; -} - static void opts_add_commit(describe_options *opts, const char *commit) { size_t sz; diff --git a/examples/general.c b/examples/general.c index ff984a36c..0780d3d49 100644 --- a/examples/general.c +++ b/examples/general.c @@ -724,6 +724,7 @@ static void config_files(const char *repo_path, git_repository* repo) int32_t autocorrect; git_config *cfg; git_config *snap_cfg; + int error_code; printf("\n*Config Listing*\n"); @@ -740,9 +741,33 @@ static void config_files(const char *repo_path, git_repository* repo) git_config_get_string(&email, snap_cfg, "user.email"); printf("Email: %s\n", email); - /** - * Remember to free the configurations after usage. - */ + error_code = git_config_get_int32(&autocorrect, cfg, "help.autocorrect"); + switch (error_code) + { + case 0: + printf("Autocorrect: %d\n", autocorrect); + break; + case GIT_ENOTFOUND: + printf("Autocorrect: Undefined\n"); + break; + default: + check_error(error_code, "get_int32 failed"); + } git_config_free(cfg); + + check_error(git_repository_config_snapshot(&snap_cfg, repo), "config snapshot"); + error_code = git_config_get_string(&email, snap_cfg, "user.email"); + switch (error_code) + { + case 0: + printf("Email: %s\n", email); + break; + case GIT_ENOTFOUND: + printf("Email: Undefined\n"); + break; + default: + check_error(error_code, "get_string failed"); + } + git_config_free(snap_cfg); } diff --git a/examples/log.c b/examples/log.c index e54eed3ce..a107470ee 100644 --- a/examples/log.c +++ b/examples/log.c @@ -50,6 +50,7 @@ static int add_revision(struct log_state *s, const char *revstr); /** log_options holds other command line options that affect log output */ struct log_options { int show_diff; + int show_log_size; int skip, limit; int min_parents, max_parents; git_time_t before; @@ -63,7 +64,7 @@ struct log_options { static int parse_options( struct log_state *s, struct log_options *opt, int argc, char **argv); static void print_time(const git_time *intime, const char *prefix); -static void print_commit(git_commit *commit); +static void print_commit(git_commit *commit, struct log_options *opts); static int match_with_parent(git_commit *commit, int i, git_diff_options *); /** utility functions for filtering */ @@ -148,7 +149,7 @@ int main(int argc, char *argv[]) break; } - print_commit(commit); + print_commit(commit, &opt); if (opt.show_diff) { git_tree *a = NULL, *b = NULL; @@ -337,7 +338,7 @@ static void print_time(const git_time *intime, const char *prefix) } /** Helper to print a commit object. */ -static void print_commit(git_commit *commit) +static void print_commit(git_commit *commit, struct log_options *opts) { char buf[GIT_OID_HEXSZ + 1]; int i, count; @@ -347,6 +348,10 @@ static void print_commit(git_commit *commit) git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); printf("commit %s\n", buf); + if (opts->show_log_size) { + printf("log size %d\n", (int)strlen(git_commit_message(commit))); + } + if ((count = (int)git_commit_parentcount(commit)) > 1) { printf("Merge:"); for (i = 0; i < count; ++i) { @@ -470,6 +475,8 @@ static int parse_options( /** Found valid --min_parents. */; else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) opt->show_diff = 1; + else if (!strcmp(a, "--log-size")) + opt->show_log_size = 1; else usage("Unsupported argument", a); } diff --git a/examples/merge.c b/examples/merge.c new file mode 100644 index 000000000..59982cb9b --- /dev/null +++ b/examples/merge.c @@ -0,0 +1,396 @@ +/* + * libgit2 "merge" example - shows how to perform merges + * + * 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 "common.h" +#include + +#ifdef _MSC_VER +#define snprintf sprintf_s +#endif + +/** The following example demonstrates how to do merges with libgit2. + * + * It will merge whatever commit-ish you pass in into the current branch. + * + * Recognized options are : + * --no-commit: don't actually commit the merge. + * + */ + +typedef struct { + const char **heads; + size_t heads_count; + + git_annotated_commit **annotated; + size_t annotated_count; + + int no_commit : 1; +} merge_options; + +static void print_usage(void) +{ + fprintf(stderr, "usage: merge [--no-commit] \n"); + exit(1); +} + +static void merge_options_init(merge_options *opts) +{ + memset(opts, 0, sizeof(*opts)); + + opts->heads = NULL; + opts->heads_count = 0; + opts->annotated = NULL; + opts->annotated_count = 0; +} + +static void opts_add_refish(merge_options *opts, const char *refish) +{ + size_t sz; + + assert(opts != NULL); + + sz = ++opts->heads_count * sizeof(opts->heads[0]); + opts->heads = xrealloc(opts->heads, sz); + opts->heads[opts->heads_count - 1] = refish; +} + +static void parse_options(const char **repo_path, merge_options *opts, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + if (argc <= 1) + print_usage(); + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *curr = argv[args.pos]; + + if (curr[0] != '-') { + opts_add_refish(opts, curr); + } else if (!strcmp(curr, "--no-commit")) { + opts->no_commit = 1; + } else if (match_str_arg(repo_path, &args, "--git-dir")) { + continue; + } else { + print_usage(); + } + } +} + +static int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish) +{ + git_reference *ref; + int err = 0; + git_oid oid; + + assert(commit != NULL); + + err = git_reference_dwim(&ref, repo, refish); + if (err == GIT_OK) { + git_annotated_commit_from_ref(commit, repo, ref); + git_reference_free(ref); + return 0; + } + + err = git_oid_fromstr(&oid, refish); + if (err == GIT_OK) { + err = git_annotated_commit_lookup(commit, repo, &oid); + } + + return err; +} + +static int resolve_heads(git_repository *repo, merge_options *opts) +{ + git_annotated_commit **annotated = calloc(opts->heads_count, sizeof(git_annotated_commit *)); + size_t annotated_count = 0, i; + int err = 0; + + for (i = 0; i < opts->heads_count; i++) { + err = resolve_refish(&annotated[annotated_count++], repo, opts->heads[i]); + if (err != 0) { + fprintf(stderr, "failed to resolve refish %s: %s\n", opts->heads[i], giterr_last()->message); + annotated_count--; + continue; + } + } + + if (annotated_count != opts->heads_count) { + fprintf(stderr, "unable to parse some refish\n"); + free(annotated); + return -1; + } + + opts->annotated = annotated; + opts->annotated_count = annotated_count; + return 0; +} + +static int perform_fastforward(git_repository *repo, const git_oid *target_oid, int is_unborn) +{ + git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *target_ref; + git_reference *new_target_ref; + git_object *target = NULL; + int err = 0; + + if (is_unborn) { + const char *symbolic_ref; + git_reference *head_ref; + + /* HEAD reference is unborn, lookup manually so we don't try to resolve it */ + err = git_reference_lookup(&head_ref, repo, "HEAD"); + if (err != 0) { + fprintf(stderr, "failed to lookup HEAD ref\n"); + return -1; + } + + /* Grab the reference HEAD should be pointing to */ + symbolic_ref = git_reference_symbolic_target(head_ref); + + /* Create our master reference on the target OID */ + err = git_reference_create(&target_ref, repo, symbolic_ref, target_oid, 0, NULL); + if (err != 0) { + fprintf(stderr, "failed to create master reference\n"); + return -1; + } + + git_reference_free(head_ref); + } else { + /* HEAD exists, just lookup and resolve */ + err = git_repository_head(&target_ref, repo); + if (err != 0) { + fprintf(stderr, "failed to get HEAD reference\n"); + return -1; + } + } + + /* Lookup the target object */ + err = git_object_lookup(&target, repo, target_oid, GIT_OBJ_COMMIT); + if (err != 0) { + fprintf(stderr, "failed to lookup OID %s\n", git_oid_tostr_s(target_oid)); + return -1; + } + + /* Checkout the result so the workdir is in the expected state */ + ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + err = git_checkout_tree(repo, target, &ff_checkout_options); + if (err != 0) { + fprintf(stderr, "failed to checkout HEAD reference\n"); + return -1; + } + + /* Move the target reference to the target OID */ + err = git_reference_set_target(&new_target_ref, target_ref, target_oid, NULL); + if (err != 0) { + fprintf(stderr, "failed to move HEAD reference\n"); + return -1; + } + + git_reference_free(target_ref); + git_reference_free(new_target_ref); + git_object_free(target); + + return 0; +} + +static void output_conflicts(git_index *index) +{ + git_index_conflict_iterator *conflicts; + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + int err = 0; + + check_lg2(git_index_conflict_iterator_new(&conflicts, index), "failed to create conflict iterator", NULL); + + while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts)) == 0) { + fprintf(stderr, "conflict: a:%s o:%s t:%s\n", + ancestor ? ancestor->path : "NULL", + our->path ? our->path : "NULL", + their->path ? their->path : "NULL"); + } + + if (err != GIT_ITEROVER) { + fprintf(stderr, "error iterating conflicts\n"); + } + + git_index_conflict_iterator_free(conflicts); +} + +static int create_merge_commit(git_repository *repo, git_index *index, merge_options *opts) +{ + git_oid tree_oid, commit_oid; + git_tree *tree; + git_signature *sign; + git_reference *merge_ref = NULL; + git_annotated_commit *merge_commit; + git_reference *head_ref; + git_commit **parents = calloc(opts->annotated_count + 1, sizeof(git_commit *)); + const char *msg_target = NULL; + size_t msglen = 0; + char *msg; + size_t i; + int err; + + /* Grab our needed references */ + check_lg2(git_repository_head(&head_ref, repo), "failed to get repo HEAD", NULL); + if (resolve_refish(&merge_commit, repo, opts->heads[0])) { + fprintf(stderr, "failed to resolve refish %s", opts->heads[0]); + return -1; + } + + /* Maybe that's a ref, so DWIM it */ + err = git_reference_dwim(&merge_ref, repo, opts->heads[0]); + check_lg2(err, "failed to DWIM reference", giterr_last()->message); + + /* Grab a signature */ + check_lg2(git_signature_now(&sign, "Me", "me@example.com"), "failed to create signature", NULL); + +#define MERGE_COMMIT_MSG "Merge %s '%s'" + /* Prepare a standard merge commit message */ + if (merge_ref != NULL) { + check_lg2(git_branch_name(&msg_target, merge_ref), "failed to get branch name of merged ref", NULL); + } else { + msg_target = git_oid_tostr_s(git_annotated_commit_id(merge_commit)); + } + + msglen = snprintf(NULL, 0, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); + if (msglen > 0) msglen++; + msg = malloc(msglen); + err = snprintf(msg, msglen, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); + + /* This is only to silence the compiler */ + if (err < 0) goto cleanup; + + /* Setup our parent commits */ + err = git_reference_peel((git_object **)&parents[0], head_ref, GIT_OBJ_COMMIT); + check_lg2(err, "failed to peel head reference", NULL); + for (i = 0; i < opts->annotated_count; i++) { + git_commit_lookup(&parents[i + 1], repo, git_annotated_commit_id(opts->annotated[i])); + } + + /* Prepare our commit tree */ + check_lg2(git_index_write_tree(&tree_oid, index), "failed to write merged tree", NULL); + check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "failed to lookup tree", NULL); + + /* Commit time ! */ + err = git_commit_create(&commit_oid, + repo, git_reference_name(head_ref), + sign, sign, + NULL, msg, + tree, + opts->annotated_count + 1, (const git_commit **)parents); + check_lg2(err, "failed to create commit", NULL); + + /* We're done merging, cleanup the repository state */ + git_repository_state_cleanup(repo); + +cleanup: + free(parents); + return err; +} + +int main(int argc, char **argv) +{ + git_repository *repo = NULL; + merge_options opts; + git_index *index; + git_repository_state_t state; + git_merge_analysis_t analysis; + git_merge_preference_t preference; + const char *path = "."; + int err = 0; + + merge_options_init(&opts); + parse_options(&path, &opts, argc, argv); + + git_libgit2_init(); + + check_lg2(git_repository_open_ext(&repo, path, 0, NULL), + "Could not open repository", NULL); + + state = git_repository_state(repo); + if (state != GIT_REPOSITORY_STATE_NONE) { + fprintf(stderr, "repository is in unexpected state %d\n", state); + goto cleanup; + } + + err = resolve_heads(repo, &opts); + if (err != 0) + goto cleanup; + + err = git_merge_analysis(&analysis, &preference, + repo, + (const git_annotated_commit **)opts.annotated, + opts.annotated_count); + check_lg2(err, "merge analysis failed", NULL); + + if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) { + printf("Already up-to-date\n"); + return 0; + } else if (analysis & GIT_MERGE_ANALYSIS_UNBORN || + (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD && + !(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) { + const git_oid *target_oid; + if (analysis & GIT_MERGE_ANALYSIS_UNBORN) { + printf("Unborn\n"); + } else { + printf("Fast-forward\n"); + } + + /* Since this is a fast-forward, there can be only one merge head */ + target_oid = git_annotated_commit_id(opts.annotated[0]); + assert(opts.annotated_count == 1); + + return perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); + } else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) { + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + merge_opts.flags = 0; + merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; + + if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) { + printf("Fast-forward is preferred, but only a merge is possible\n"); + return -1; + } + + err = git_merge(repo, + (const git_annotated_commit **)opts.annotated, opts.annotated_count, + &merge_opts, &checkout_opts); + check_lg2(err, "merge failed", NULL); + } + + /* If we get here, we actually performed the merge above */ + + check_lg2(git_repository_index(&index, repo), "failed to get repository index", NULL); + + if (git_index_has_conflicts(index)) { + /* Handle conflicts */ + output_conflicts(index); + } else if (!opts.no_commit) { + create_merge_commit(repo, index, &opts); + printf("Merge made\n"); + } + +cleanup: + free(opts.heads); + free(opts.annotated); + git_repository_free(repo); + git_libgit2_shutdown(); + + return 0; +} diff --git a/examples/network/common.c b/examples/network/common.c index 1a81a10f8..b0afb0238 100644 --- a/examples/network/common.c +++ b/examples/network/common.c @@ -16,6 +16,43 @@ # define UNUSED(x) x #endif +static int readline(char **out) +{ + int c, error = 0, length = 0, allocated = 0; + char *line = NULL; + + errno = 0; + + while ((c = getchar()) != EOF) { + if (length == allocated) { + allocated += 16; + + if ((line = realloc(line, allocated)) == NULL) { + error = -1; + goto error; + } + } + + if (c == '\n') + break; + + line[length++] = c; + } + + if (errno != 0) { + error = -1; + goto error; + } + + line[length] = '\0'; + *out = line; + line = NULL; + error = length; +error: + free(line); + return error; +} + int cred_acquire_cb(git_cred **out, const char * UNUSED(url), const char * UNUSED(username_from_url), @@ -26,14 +63,14 @@ int cred_acquire_cb(git_cred **out, int error; printf("Username: "); - if (getline(&username, NULL, stdin) < 0) { + if (readline(&username) < 0) { fprintf(stderr, "Unable to read username: %s", strerror(errno)); return -1; } /* Yup. Right there on your terminal. Careful where you copy/paste output. */ printf("Password: "); - if (getline(&password, NULL, stdin) < 0) { + if (readline(&password) < 0) { fprintf(stderr, "Unable to read password: %s", strerror(errno)); free(username); return -1; diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 10974a9f1..76b301193 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -8,13 +8,6 @@ # include #endif -struct dl_data { - git_remote *remote; - git_fetch_options *fetch_opts; - int ret; - int finished; -}; - static int progress_cb(const char *str, int len, void *data) { (void)data; diff --git a/examples/network/git2.c b/examples/network/git2.c index 448103c46..1d3d27b1b 100644 --- a/examples/network/git2.c +++ b/examples/network/git2.c @@ -2,10 +2,11 @@ #include #include +#include "../common.h" #include "common.h" -// This part is not strictly libgit2-dependent, but you can use this -// as a starting point for a git-like tool +/* This part is not strictly libgit2-dependent, but you can use this + * as a starting point for a git-like tool */ struct { char *name; @@ -18,20 +19,12 @@ struct { { NULL, NULL} }; -static int run_command(git_cb fn, int argc, char **argv) +static int run_command(git_cb fn, git_repository *repo, struct args_info args) { int error; - git_repository *repo; - // Before running the actual command, create an instance of the local - // repository and pass it to the function. - - error = git_repository_open(&repo, ".git"); - if (error < 0) - repo = NULL; - - // Run the command. If something goes wrong, print the error message to stderr - error = fn(repo, argc, argv); + /* Run the command. If something goes wrong, print the error message to stderr */ + error = fn(repo, args.argc - args.pos, &args.argv[args.pos]); if (error < 0) { if (giterr_last() == NULL) fprintf(stderr, "Error without message"); @@ -39,9 +32,6 @@ static int run_command(git_cb fn, int argc, char **argv) fprintf(stderr, "Bad news:\n %s\n", giterr_last()->message); } - if(repo) - git_repository_free(repo); - return !!error; } @@ -49,6 +39,10 @@ int main(int argc, char **argv) { int i; int return_code = 1; + int error; + git_repository *repo; + struct args_info args = ARGS_INFO_INIT; + const char *git_dir = NULL; if (argc < 2) { fprintf(stderr, "usage: %s [repo]\n", argv[0]); @@ -57,9 +51,30 @@ int main(int argc, char **argv) git_libgit2_init(); + for (args.pos = 1; args.pos < args.argc; ++args.pos) { + char *a = args.argv[args.pos]; + + if (a[0] != '-') { + /* non-arg */ + break; + } else if (optional_str_arg(&git_dir, &args, "--git-dir", ".git")) { + continue; + } else if (!strcmp(a, "--")) { + /* arg separator */ + break; + } + } + + /* Before running the actual command, create an instance of the local + * repository and pass it to the function. */ + + error = git_repository_open(&repo, git_dir); + if (error < 0) + repo = NULL; + for (i = 0; commands[i].name != NULL; ++i) { - if (!strcmp(argv[1], commands[i].name)) { - return_code = run_command(commands[i].fn, --argc, ++argv); + if (!strcmp(args.argv[args.pos], commands[i].name)) { + return_code = run_command(commands[i].fn, repo, args); goto shutdown; } } @@ -67,6 +82,8 @@ int main(int argc, char **argv) fprintf(stderr, "Command not found: %s\n", argv[1]); shutdown: + git_repository_free(repo); + git_libgit2_shutdown(); return return_code; diff --git a/git.git-authors b/git.git-authors index 6a85224b4..3bc009fcc 100644 --- a/git.git-authors +++ b/git.git-authors @@ -53,6 +53,7 @@ ok Jeff King ok Johannes Schindelin ok Johannes Sixt ask Jonathan Nieder +ok Jonathan Tan ok Junio C Hamano ok Kristian Høgsberg ok Linus Torvalds diff --git a/include/git2.h b/include/git2.h index bc4333cc9..5f6104ef7 100644 --- a/include/git2.h +++ b/include/git2.h @@ -62,5 +62,6 @@ #include "git2/tree.h" #include "git2/types.h" #include "git2/version.h" +#include "git2/worktree.h" #endif diff --git a/include/git2/config.h b/include/git2/config.h index d0f1ba1b3..d9da65b84 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -199,6 +199,8 @@ GIT_EXTERN(int) git_config_new(git_config **out); * @param path path to the configuration file to add * @param level the priority level of the backend * @param force replace config file at the given priority level + * @param repo optional repository to allow parsing of + * conditional includes * @return 0 on success, GIT_EEXISTS when adding more than one file * for a given priority level (and force_replace set to 0), * GIT_ENOTFOUND when the file doesn't exist or error code @@ -207,6 +209,7 @@ GIT_EXTERN(int) git_config_add_file_ondisk( git_config *cfg, const char *path, git_config_level_t level, + const git_repository *repo, int force); /** @@ -398,6 +401,10 @@ GIT_EXTERN(int) git_config_get_string_buf(git_buf *out, const git_config *cfg, c * * The callback will be called on each variable found * + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the section and variable parts are lower-cased. The + * subsection is left unchanged. + * * @param cfg where to look for the variable * @param name the variable's name * @param regexp regular expression to filter which variables we're @@ -410,6 +417,10 @@ GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const cha /** * Get each value of a multivar * + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the section and variable parts are lower-cased. The + * subsection is left unchanged. + * * @param out pointer to store the iterator * @param cfg where to look for the variable * @param name the variable's name @@ -487,6 +498,8 @@ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const c /** * Set a multivar in the local config file. * + * The regular expression is applied case-sensitively on the value. + * * @param cfg where to look for the variable * @param name the variable's name * @param regexp a regular expression to indicate which values to replace @@ -506,6 +519,8 @@ GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name); /** * Deletes one or several entries from a multivar in the local config file. * + * The regular expression is applied case-sensitively on the value. + * * @param cfg where to look for the variables * @param name the variable's name * @param regexp a regular expression to indicate which values to delete @@ -552,6 +567,10 @@ GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_con * Use `git_config_next` to advance the iteration and * `git_config_iterator_free` when done. * + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the section and variable parts are lower-cased. The + * subsection is left unchanged. + * * @param out pointer to store the iterator * @param cfg where to ge the variables from * @param regexp regular expression to match the names @@ -565,8 +584,12 @@ GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const gi * regular expression that filters which config keys are passed to the * callback. * - * The pointers passed to the callback are only valid as long as the - * iteration is ongoing. + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the section and variable parts are lower-cased. The + * subsection is left unchanged. + * + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the case-insensitive parts are lower-case. * * @param cfg where to get the variables from * @param regexp regular expression to match against config names @@ -693,6 +716,10 @@ GIT_EXTERN(int) git_config_parse_path(git_buf *out, const char *value); * This behaviors like `git_config_foreach_match` except instead of all config * entries it just enumerates through the given backend entry. * + * The regular expression is applied case-sensitively on the normalized form of + * the variable name: the section and variable parts are lower-cased. The + * subsection is left unchanged. + * * @param backend where to get the variables from * @param regexp regular expression to match against config names (can be NULL) * @param callback the function to call on each variable diff --git a/include/git2/describe.h b/include/git2/describe.h index 971eb350c..63de7e18c 100644 --- a/include/git2/describe.h +++ b/include/git2/describe.h @@ -23,7 +23,7 @@ GIT_BEGIN_DECL /** * Reference lookup strategy * - * These behave like the --tags and --all optios to git-describe, + * These behave like the --tags and --all options to git-describe, * namely they say to look for any reference in either refs/tags/ or * refs/ respectively. */ diff --git a/include/git2/diff.h b/include/git2/diff.h index 4f0871dab..86009f583 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -200,12 +200,18 @@ typedef enum { /** Use the "patience diff" algorithm */ GIT_DIFF_PATIENCE = (1u << 28), /** Take extra time to find minimal diff */ - GIT_DIFF_MINIMAL = (1 << 29), + GIT_DIFF_MINIMAL = (1u << 29), /** Include the necessary deflate / delta information so that `git-apply` * can apply given diff information to binary files. */ - GIT_DIFF_SHOW_BINARY = (1 << 30), + GIT_DIFF_SHOW_BINARY = (1u << 30), + + /** Use a heuristic that takes indentation and whitespace into account + * which generally can produce better diffs when dealing with ambiguous + * diff hunks. + */ + GIT_DIFF_INDENT_HEURISTIC = (1u << 31), } git_diff_option_t; /** @@ -515,12 +521,12 @@ typedef int(*git_diff_binary_cb)( * Structure describing a hunk of a diff. */ typedef struct { - int old_start; /** Starting line number in old_file */ - int old_lines; /** Number of lines in old_file */ - int new_start; /** Starting line number in new_file */ - int new_lines; /** Number of lines in new_file */ - size_t header_len; /** Number of bytes in header text */ - char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */ + int old_start; /**< Starting line number in old_file */ + int old_lines; /**< Number of lines in old_file */ + int new_start; /**< Starting line number in new_file */ + int new_lines; /**< Number of lines in new_file */ + size_t header_len; /**< Number of bytes in header text */ + char header[GIT_DIFF_HUNK_HEADER_SIZE]; /**< Header text, NUL-byte terminated */ } git_diff_hunk; /** @@ -1400,6 +1406,51 @@ GIT_EXTERN(int) git_diff_format_email_init_options( git_diff_format_email_options *opts, unsigned int version); +/** + * Patch ID options structure + * + * Initialize with `GIT_DIFF_PATCHID_OPTIONS_INIT` macro to + * correctly set the default values and version. + */ +typedef struct git_diff_patchid_options { + unsigned int version; +} git_diff_patchid_options; + +#define GIT_DIFF_PATCHID_OPTIONS_VERSION 1 +#define GIT_DIFF_PATCHID_OPTIONS_INIT { GIT_DIFF_PATCHID_OPTIONS_VERSION } + +/** + * Initialize `git_diff_patchid_options` structure. + * + * Initializes the structure with default values. Equivalent to + * creating an instance with `GIT_DIFF_PATCHID_OPTIONS_INIT`. + */ +GIT_EXTERN(int) git_diff_patchid_init_options( + git_diff_patchid_options *opts, + unsigned int version); + +/** + * Calculate the patch ID for the given patch. + * + * Calculate a stable patch ID for the given patch by summing the + * hash of the file diffs, ignoring whitespace and line numbers. + * This can be used to derive whether two diffs are the same with + * a high probability. + * + * Currently, this function only calculates stable patch IDs, as + * defined in git-patch-id(1), and should in fact generate the + * same IDs as the upstream git project does. + * + * @param out Pointer where the calculated patch ID shoul be + * stored + * @param diff The diff to calculate the ID for + * @param opts Options for how to calculate the patch ID. This is + * intended for future changes, as currently no options are + * available. + * @return 0 on success, an error code otherwise. + */ +GIT_EXTERN(int) git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts); + GIT_END_DECL /** @} */ diff --git a/include/git2/graph.h b/include/git2/graph.h index c997d8ca9..213ae9777 100644 --- a/include/git2/graph.h +++ b/include/git2/graph.h @@ -40,6 +40,9 @@ GIT_EXTERN(int) git_graph_ahead_behind(size_t *ahead, size_t *behind, git_reposi /** * Determine if a commit is the descendant of another commit. * + * Note that a commit is not considered a descendant of itself, in contrast + * to `git merge-base --is-ancestor`. + * * @param commit a previously loaded commit. * @param ancestor a potential ancestor commit. * @return 1 if the given commit is a descendant of the potential ancestor, diff --git a/include/git2/merge.h b/include/git2/merge.h index 94ac8b5c5..80ef864d1 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -162,6 +162,8 @@ typedef enum { GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), } git_merge_file_flag_t; +#define GIT_MERGE_CONFLICT_MARKER_SIZE 7 + /** * Options for merging a file */ @@ -191,6 +193,10 @@ typedef struct { /** see `git_merge_file_flag_t` above */ git_merge_file_flag_t flags; + + /** The size of conflict markers (eg, "<<<<<<<"). Default is + * GIT_MERGE_CONFLICT_MARKER_SIZE. */ + unsigned short marker_size; } git_merge_file_options; #define GIT_MERGE_FILE_OPTIONS_VERSION 1 diff --git a/include/git2/message.h b/include/git2/message.h index d78b1dce5..42ca3e5c2 100644 --- a/include/git2/message.h +++ b/include/git2/message.h @@ -19,10 +19,9 @@ GIT_BEGIN_DECL /** - * Clean up message from excess whitespace and make sure that the last line - * ends with a '\n'. + * Clean up excess whitespace and make sure there is a trailing newline in the message. * - * Optionally, can remove lines starting with a "#". + * Optionally, it can remove lines which start with the comment character. * * @param out The user-allocated git_buf which will be filled with the * cleaned up message. @@ -38,6 +37,47 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments, char comment_char); +/** + * Represents a single git message trailer. + */ +typedef struct { + const char *key; + const char *value; +} git_message_trailer; + +/** + * Represents an array of git message trailers. + * + * Struct members under the private comment are private, subject to change + * and should not be used by callers. + */ +typedef struct { + git_message_trailer *trailers; + size_t count; + + /* private */ + char *_trailer_block; +} git_message_trailer_array; + +/** + * Parse trailers out of a message, filling the array pointed to by +arr+. + * + * Trailers are key/value pairs in the last paragraph of a message, not + * including any patches or conflicts that may be present. + * + * @param arr A pre-allocated git_message_trailer_array struct to be filled in + * with any trailers found during parsing. + * @param message The message to be parsed + * @return 0 on success, or non-zero on error. + */ +GIT_EXTERN(int) git_message_trailers(git_message_trailer_array *arr, const char *message); + +/** + * Clean's up any allocated memory in the git_message_trailer_array filled by + * a call to git_message_trailers. + */ +GIT_EXTERN(void) git_message_trailer_array_free(git_message_trailer_array *arr); + /** @} */ GIT_END_DECL diff --git a/include/git2/notes.h b/include/git2/notes.h index 3a626cafd..853e5de59 100644 --- a/include/git2/notes.h +++ b/include/git2/notes.h @@ -51,6 +51,20 @@ GIT_EXTERN(int) git_note_iterator_new( git_repository *repo, const char *notes_ref); +/** + * Creates a new iterator for notes from a commit + * + * The iterator must be freed manually by the user. + * + * @param out pointer to the iterator + * @param notes_commit a pointer to the notes commit object + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_commit_iterator_new( + git_note_iterator **out, + git_commit *notes_commit); + /** * Frees an git_note_iterator * @@ -94,6 +108,25 @@ GIT_EXTERN(int) git_note_read( const char *notes_ref, const git_oid *oid); + +/** + * Read the note for an object from a note commit + * + * The note must be freed manually by the user. + * + * @param out pointer to the read note; NULL in case of error + * @param repo repository where to look up the note + * @param notes_commit a pointer to the notes commit object + * @param oid OID of the git object to read the note from + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_commit_read( + git_note **out, + git_repository *repo, + git_commit *notes_commit, + const git_oid *oid); + /** * Get the note author * @@ -153,6 +186,36 @@ GIT_EXTERN(int) git_note_create( const char *note, int force); +/** + * Add a note for an object from a commit + * + * This function will create a notes commit for a given object, + * the commit is a dangling commit, no reference is created. + * + * @param notes_commit_out pointer to store the commit (optional); + * NULL in case of error + * @param notes_blob_out a point to the id of a note blob (optional) + * @param repo repository where the note will live + * @param parent Pointer to parent note + * or NULL if this shall start a new notes tree + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param oid OID of the git object to decorate + * @param note Content of the note to add for object oid + * @param allow_note_overwrite Overwrite existing note + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_commit_create( + git_oid *notes_commit_out, + git_oid *notes_blob_out, + git_repository *repo, + git_commit *parent, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite); /** * Remove the note for an object @@ -173,6 +236,32 @@ GIT_EXTERN(int) git_note_remove( const git_signature *committer, const git_oid *oid); +/** + * Remove the note for an object + * + * @param notes_commit_out pointer to store the new notes commit (optional); + * NULL in case of error. + * When removing a note a new tree containing all notes + * sans the note to be removed is created and a new commit + * pointing to that tree is also created. + * In the case where the resulting tree is an empty tree + * a new commit pointing to this empty tree will be returned. + * @param repo repository where the note lives + * @param notes_commit a pointer to the notes commit object + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param oid OID of the git object to remove the note from + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_commit_remove( + git_oid *notes_commit_out, + git_repository *repo, + git_commit *notes_commit, + const git_signature *author, + const git_signature *committer, + const git_oid *oid); + /** * Free a git_note object * diff --git a/include/git2/odb.h b/include/git2/odb.h index b7dc0c5f3..006a75b7a 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -357,11 +357,18 @@ GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream); * @see git_odb_stream * * @param out pointer where to store the stream + * @param len pointer where to store the length of the object + * @param type pointer where to store the type of the object * @param db object database where the stream will read from * @param oid oid of the object the stream will read from * @return 0 if the stream was created; error code otherwise */ -GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **out, git_odb *db, const git_oid *oid); +GIT_EXTERN(int) git_odb_open_rstream( + git_odb_stream **out, + size_t *len, + git_otype *type, + git_odb *db, + const git_oid *oid); /** * Open a stream for writing a pack file to the ODB. diff --git a/include/git2/patch.h b/include/git2/patch.h index 4eb9f0263..b177798e6 100644 --- a/include/git2/patch.h +++ b/include/git2/patch.h @@ -96,7 +96,7 @@ GIT_EXTERN(int) git_patch_from_blob_and_buffer( git_patch **out, const git_blob *old_blob, const char *old_as_path, - const char *buffer, + const void *buffer, size_t buffer_len, const char *buffer_as_path, const git_diff_options *opts); @@ -124,7 +124,7 @@ GIT_EXTERN(int) git_patch_from_buffers( const void *old_buffer, size_t old_len, const char *old_as_path, - const char *new_buffer, + const void *new_buffer, size_t new_len, const char *new_as_path, const git_diff_options *opts); diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h index de6f027c5..329965789 100644 --- a/include/git2/pathspec.h +++ b/include/git2/pathspec.h @@ -26,32 +26,49 @@ typedef struct git_pathspec_match_list git_pathspec_match_list; /** * Options controlling how pathspec match should be executed - * - * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise - * match will use native case sensitivity of platform filesystem - * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise - * match will use native case sensitivity of platform filesystem - * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple - * string comparison for matching - * - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error - * code GIT_ENOTFOUND if no matches are found; otherwise no matches is - * still success (return 0) but `git_pathspec_match_list_entrycount` - * will indicate 0 matches. - * - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list` - * should track which patterns matched which files so that at the end of - * the match we can identify patterns that did not match any files. - * - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list` - * does not need to keep the actual matching filenames. Use this to - * just test if there were any matches at all or in combination with - * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec. */ typedef enum { GIT_PATHSPEC_DEFAULT = 0, + + /** + * GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise + * match will use native case sensitivity of platform filesystem + */ GIT_PATHSPEC_IGNORE_CASE = (1u << 0), + + /** + * GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise + * match will use native case sensitivity of platform filesystem + */ GIT_PATHSPEC_USE_CASE = (1u << 1), + + /** + * GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple + * string comparison for matching + */ GIT_PATHSPEC_NO_GLOB = (1u << 2), + + /** + * GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error + * code GIT_ENOTFOUND if no matches are found; otherwise no matches is + * still success (return 0) but `git_pathspec_match_list_entrycount` + * will indicate 0 matches. + */ GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3), + + /** + * GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list` + * should track which patterns matched which files so that at the end of + * the match we can identify patterns that did not match any files. + */ GIT_PATHSPEC_FIND_FAILURES = (1u << 4), + + /** + * GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list` + * does not need to keep the actual matching filenames. Use this to + * just test if there were any matches at all or in combination with + * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec. + */ GIT_PATHSPEC_FAILURES_ONLY = (1u << 5), } git_pathspec_flag_t; diff --git a/include/git2/refs.h b/include/git2/refs.h index dee28cb5b..0dd453e55 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -433,6 +433,9 @@ typedef int (*git_reference_foreach_name_cb)(const char *name, void *payload); * passed to this method. Returning a non-zero value from the callback * will terminate the iteration. * + * Note that the callback function is responsible to call `git_reference_free` + * on each reference passed to it. + * * @param repo Repository where to find the refs * @param callback Function which will be called for every listed ref * @param payload Additional data to pass to the callback diff --git a/include/git2/remote.h b/include/git2/remote.h index e9e4e5b65..22e2291d0 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -75,6 +75,24 @@ GIT_EXTERN(int) git_remote_create_anonymous( git_repository *repo, const char *url); +/** + * Create a remote without a connected local repo + * + * Create a remote with the given url in-memory. You can use this when + * you have a URL instead of a remote's name. + * + * Contrasted with git_remote_create_anonymous, a detached remote + * will not consider any repo configuration values (such as insteadof url + * substitutions). + * + * @param out pointer to the new remote objects + * @param url the remote repository's URL + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_create_detached( + git_remote **out, + const char *url); + /** * Get the information for a particular remote * @@ -367,6 +385,20 @@ typedef struct { */ typedef int (*git_push_negotiation)(const git_push_update **updates, size_t len, void *payload); +/** + * Callback used to inform of the update status from the remote. + * + * Called for each updated reference on push. If `status` is + * not `NULL`, the update was rejected by the remote server + * and `status` contains the reason given. + * + * @param refname refname specifying to the remote ref + * @param status status message sent from the remote + * @param data data provided by the caller + * @return 0 on success, otherwise an error + */ +typedef int (*git_push_update_reference_cb)(const char *refname, const char *status, void *data); + /** * The callback settings structure * @@ -434,11 +466,9 @@ struct git_remote_callbacks { git_push_transfer_progress push_transfer_progress; /** - * Called for each updated reference on push. If `status` is - * not `NULL`, the update was rejected by the remote server - * and `status` contains the reason given. + * See documentation of git_push_update_reference_cb */ - int (*push_update_reference)(const char *refname, const char *status, void *data); + git_push_update_reference_cb push_update_reference; /** * Called once between the negotiation step and the upload. It diff --git a/include/git2/repository.h b/include/git2/repository.h index 8aac0b3f7..6e0c1f71e 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -440,7 +440,7 @@ typedef enum { * @param item The repository item for which to retrieve the path * @return 0, GIT_ENOTFOUND if the path cannot exist or an error code */ -GIT_EXTERN(int) git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item); +GIT_EXTERN(int) git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item); /** * Get the path of this repository @@ -451,7 +451,7 @@ GIT_EXTERN(int) git_repository_item_path(git_buf *out, git_repository *repo, git * @param repo A repository object * @return the path to the repository */ -GIT_EXTERN(const char *) git_repository_path(git_repository *repo); +GIT_EXTERN(const char *) git_repository_path(const git_repository *repo); /** * Get the path of the working directory for this repository @@ -462,7 +462,7 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo); * @param repo A repository object * @return the path to the working dir, if it exists */ -GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); +GIT_EXTERN(const char *) git_repository_workdir(const git_repository *repo); /** * Get the path of the shared common directory for this repository @@ -473,7 +473,7 @@ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); * @param repo A repository object * @return the path to the common dir */ -GIT_EXTERN(const char *) git_repository_commondir(git_repository *repo); +GIT_EXTERN(const char *) git_repository_commondir(const git_repository *repo); /** * Set the path to the working directory for this repository @@ -501,7 +501,7 @@ GIT_EXTERN(int) git_repository_set_workdir( * @param repo Repo to test * @return 1 if the repository is bare, 0 otherwise. */ -GIT_EXTERN(int) git_repository_is_bare(git_repository *repo); +GIT_EXTERN(int) git_repository_is_bare(const git_repository *repo); /** * Check if a repository is a linked work tree @@ -509,7 +509,7 @@ GIT_EXTERN(int) git_repository_is_bare(git_repository *repo); * @param repo Repo to test * @return 1 if the repository is a linked work tree, 0 otherwise. */ -GIT_EXTERN(int) git_repository_is_worktree(git_repository *repo); +GIT_EXTERN(int) git_repository_is_worktree(const git_repository *repo); /** * Get the configuration file for this repository. diff --git a/include/git2/reset.h b/include/git2/reset.h index bd29c69e4..be2541433 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -61,7 +61,7 @@ typedef enum { */ GIT_EXTERN(int) git_reset( git_repository *repo, - git_object *target, + const git_object *target, git_reset_t reset_type, const git_checkout_options *checkout_opts); @@ -79,7 +79,7 @@ GIT_EXTERN(int) git_reset( */ GIT_EXTERN(int) git_reset_from_annotated( git_repository *repo, - git_annotated_commit *commit, + const git_annotated_commit *commit, git_reset_t reset_type, const git_checkout_options *checkout_opts); @@ -103,8 +103,8 @@ GIT_EXTERN(int) git_reset_from_annotated( */ GIT_EXTERN(int) git_reset_default( git_repository *repo, - git_object *target, - git_strarray* pathspecs); + const git_object *target, + const git_strarray* pathspecs); /** @} */ GIT_END_DECL diff --git a/include/git2/status.h b/include/git2/status.h index 671113955..4b86818b7 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -173,12 +173,16 @@ typedef enum { * The `pathspec` is an array of path patterns to match (using * fnmatch-style matching), or just an array of paths to match exactly if * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags. + * + * The `baseline` is the tree to be used for comparison to the working directory + * and index; defaults to HEAD. */ typedef struct { unsigned int version; git_status_show_t show; unsigned int flags; git_strarray pathspec; + git_tree *baseline; } git_status_options; #define GIT_STATUS_OPTIONS_VERSION 1 diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 4dad6da42..ed203226f 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -58,7 +58,7 @@ struct git_config_backend { struct git_config *cfg; /* Open means open the file/database and parse if necessary */ - int (*open)(struct git_config_backend *, git_config_level_t level); + int (*open)(struct git_config_backend *, git_config_level_t level, const git_repository *repo); int (*get)(struct git_config_backend *, const char *key, git_config_entry **entry); int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); @@ -111,6 +111,8 @@ GIT_EXTERN(int) git_config_init_backend( * @param cfg the configuration to add the file to * @param file the configuration file (backend) to add * @param level the priority level of the backend + * @param repo optional repository to allow parsing of + * conditional includes * @param force if a config file already exists for the given * priority level, replace it * @return 0 on success, GIT_EEXISTS when adding more than one file @@ -120,6 +122,7 @@ GIT_EXTERN(int) git_config_add_backend( git_config *cfg, git_config_backend *file, git_config_level_t level, + const git_repository *repo, int force); /** @} */ diff --git a/include/git2/sys/mempack.h b/include/git2/sys/mempack.h index 96074fb77..490636b44 100644 --- a/include/git2/sys/mempack.h +++ b/include/git2/sys/mempack.h @@ -11,6 +11,7 @@ #include "git2/types.h" #include "git2/oid.h" #include "git2/odb.h" +#include "git2/buffer.h" /** * @file git2/sys/mempack.h @@ -38,10 +39,10 @@ GIT_BEGIN_DECL * Subsequent reads will also be served from the in-memory store * to ensure consistency, until the memory store is dumped. * - * @param out Poiter where to store the ODB backend + * @param out Pointer where to store the ODB backend * @return 0 on success; error code otherwise */ -int git_mempack_new(git_odb_backend **out); +GIT_EXTERN(int) git_mempack_new(git_odb_backend **out); /** * Dump all the queued in-memory writes to a packfile. @@ -64,7 +65,7 @@ int git_mempack_new(git_odb_backend **out); * @param backend The mempack backend * @return 0 on success; error code otherwise */ -int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backend); +GIT_EXTERN(int) git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backend); /** * Reset the memory packer by clearing all the queued objects. @@ -78,7 +79,7 @@ int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backe * * @param backend The mempack backend */ -void git_mempack_reset(git_odb_backend *backend); +GIT_EXTERN(void) git_mempack_reset(git_odb_backend *backend); GIT_END_DECL diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 9bcc50ddd..792f103fe 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -56,7 +56,8 @@ struct git_odb_backend { git_odb_stream **, git_odb_backend *, git_off_t, git_otype); int (* readstream)( - git_odb_stream **, git_odb_backend *, const git_oid *); + git_odb_stream **, size_t *, git_otype *, + git_odb_backend *, const git_oid *); int (* exists)( git_odb_backend *, const git_oid *); diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h deleted file mode 100644 index 3037b411c..000000000 --- a/include/git2/sys/remote.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_sys_git_transport_h -#define INCLUDE_sys_git_transport_h - -#include "git2/net.h" -#include "git2/types.h" - -GIT_BEGIN_DECL - -GIT_END_DECL diff --git a/include/git2/tree.h b/include/git2/tree.h index 4740b1ffa..1a363c149 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -307,9 +307,10 @@ GIT_EXTERN(const git_tree_entry *) git_treebuilder_get( * pointer may not be valid past the next operation in this * builder. Duplicate the entry if you want to keep it. * - * No attempt is being made to ensure that the provided oid points - * to an existing git object in the object database, nor that the - * attributes make sense regarding the type of the pointed at object. + * By default the entry that you are inserting will be checked for + * validity; that it exists in the object database and is of the + * correct type. If you do not want this behavior, set the + * `GIT_OPT_ENABLE_STRICT_OBJECT_CREATION` library option to false. * * @param out Pointer to store the entry (optional) * @param bld Tree builder diff --git a/include/git2/types.h b/include/git2/types.h index dfdaa2920..8d9a94710 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -159,6 +159,7 @@ typedef struct git_packbuilder git_packbuilder; typedef struct git_time { git_time_t time; /**< time in seconds from epoch */ int offset; /**< timezone offset, in minutes */ + char sign; /**< indicator for questionable '-0000' offsets in signature */ } git_time; /** An action signature (e.g. for committers, taggers, etc) */ diff --git a/include/git2/version.h b/include/git2/version.h index becf97bd5..746bf4230 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,12 +7,12 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.26.0" +#define LIBGIT2_VERSION "0.27.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 26 +#define LIBGIT2_VER_MINOR 27 #define LIBGIT2_VER_REVISION 0 #define LIBGIT2_VER_PATCH 0 -#define LIBGIT2_SOVERSION 26 +#define LIBGIT2_SOVERSION 27 #endif diff --git a/include/git2/worktree.h b/include/git2/worktree.h index d3fa88e3f..a2a5d4473 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -123,7 +123,7 @@ GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, * @param reason Reason why the working tree is being locked * @return 0 on success, non-zero otherwise */ -GIT_EXTERN(int) git_worktree_lock(git_worktree *wt, char *reason); +GIT_EXTERN(int) git_worktree_lock(git_worktree *wt, const char *reason); /** * Unlock a locked worktree diff --git a/script/appveyor-mingw.sh b/script/appveyor-mingw.sh index d171a72d5..6b2a9425e 100755 --- a/script/appveyor-mingw.sh +++ b/script/appveyor-mingw.sh @@ -19,5 +19,5 @@ fi cd build gcc --version cmake --version -cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON .. -G"$GENERATOR" +cmake -D ENABLE_TRACE=ON -D BUILD_CLAR=ON -D BUILD_EXAMPLES=ON .. -G"$GENERATOR" cmake --build . --config RelWithDebInfo diff --git a/script/cibuild.sh b/script/cibuild.sh index 403df223e..5d70e7506 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -10,6 +10,15 @@ fi if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=$(ls -d /usr/local/Cellar/{curl,zlib}/*/lib/pkgconfig | paste -s -d':' -) + + # Set up a ramdisk for us to put our test data on to speed up tests on macOS + export CLAR_TMP="$HOME"/_clar_tmp + mkdir -p $CLAR_TMP + + # 5*2M sectors aka ~5GB of space + device=$(hdiutil attach -nomount ram://$((5 * 2 * 1024 * 1024))) + newfs_hfs $device + mount -t hfs $device $CLAR_TMP fi # Should we ask Travis to cache this file? @@ -43,46 +52,51 @@ ctest -V -R libgit2_clar || exit $? killall git-daemon -if [ "$TRAVIS_OS_NAME" = "osx" ]; then - echo 'PasswordAuthentication yes' | sudo tee -a /etc/sshd_config -fi +# Set up sshd +mkdir ~/sshd/ +cat >~/sshd/sshd_config<<-EOF + Port 2222 + ListenAddress 0.0.0.0 + Protocol 2 + HostKey ${HOME}/sshd/id_rsa + PidFile ${HOME}/sshd/pid + RSAAuthentication yes + PasswordAuthentication yes + PubkeyAuthentication yes + ChallengeResponseAuthentication no + # Required here as sshd will simply close connection otherwise + UsePAM no +EOF +ssh-keygen -t rsa -f ~/sshd/id_rsa -N "" -q +/usr/sbin/sshd -f ~/sshd/sshd_config +# Set up keys ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys -ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts +while read algorithm key comment; do + echo "[localhost]:2222 $algorithm $key" >>~/.ssh/known_hosts +done <~/sshd/id_rsa.pub # Get the fingerprint for localhost and remove the colons so we can parse it as # a hex number. The Mac version is newer so it has a different output format. if [ "$TRAVIS_OS_NAME" = "osx" ]; then - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -E md5 -F localhost -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) + export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -E md5 -F '[localhost]:2222' -l | tail -n 1 | cut -d ' ' -f 3 | cut -d : -f2- | tr -d :) else - export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F localhost -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') + export GITTEST_REMOTE_SSH_FINGERPRINT=$(ssh-keygen -F '[localhost]:2222' -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':') fi -export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git" +# Use the SSH server +export GITTEST_REMOTE_URL="ssh://localhost:2222/$HOME/_temp/test.git" export GITTEST_REMOTE_USER=$USER export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" export GITTEST_REMOTE_SSH_PASSPHRASE="" +ctest -V -R libgit2_clar-ssh || exit $? +# Use the proxy we started at the beginning +export GITTEST_REMOTE_PROXY_URL="localhost:8080" +export GITTEST_REMOTE_PROXY_USER="foo" +export GITTEST_REMOTE_PROXY_PASS="bar" +ctest -V -R libgit2_clar-proxy_credentials || exit $? -if [ -e ./libgit2_clar ]; then - ./libgit2_clar -sonline::push -sonline::clone::ssh_cert && - ./libgit2_clar -sonline::clone::ssh_with_paths || exit $? - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./libgit2_clar -sonline::clone::cred_callback || exit $? - fi - - # Use the proxy we started at the beginning - export GITTEST_REMOTE_PROXY_URL="http://foo:bar@localhost:8080/" - ./libgit2_clar -sonline::clone::proxy_credentials_in_url || exit $? - export GITTEST_REMOTE_PROXY_URL="http://localhost:8080/" - export GITTEST_REMOTE_PROXY_USER="foo" - export GITTEST_REMOTE_PROXY_PASS="bar" - ./libgit2_clar -sonline::clone::proxy_credentials_request || exit $? - -fi - -export GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" -export GITTEST_REMOTE_USER="libgit2test" -ctest -V -R libgit2_clar-cred_callback +kill $(cat "$HOME/sshd/pid") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..b03b96af9 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,463 @@ +IF(DEBUG_POOL) + SET(GIT_DEBUG_POOL 1) +ENDIF() +ADD_FEATURE_INFO(debugpool GIT_DEBUG_POOL "debug pool allocator") + +# This variable will contain the libraries we need to put into +# libgit2.pc's Requires.private. That is, what we're linking to or +# what someone who's statically linking us needs to link to. +SET(LIBGIT2_PC_REQUIRES "") +# This will be set later if we use the system's http-parser library or +# use iconv (OSX) and will be written to the Libs.private field in the +# pc file. +SET(LIBGIT2_PC_LIBS "") + +SET(LIBGIT2_INCLUDES + "${CMAKE_CURRENT_BINARY_DIR}" + "${libgit2_SOURCE_DIR}/src" + "${libgit2_SOURCE_DIR}/include") +SET(LIBGIT2_LIBS "") +SET(LIBGIT2_LIBDIRS "") + +# Installation paths +# +SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.") +SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.") +SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.") + +# Set a couple variables to be substituted inside the .pc file. +# We can't just use LIB_INSTALL_DIR in the .pc file, as passing them as absolue +# or relative paths is both valid and supported by cmake. +SET (PKGCONFIG_PREFIX ${CMAKE_INSTALL_PREFIX}) + +IF(IS_ABSOLUTE ${LIB_INSTALL_DIR}) + SET (PKGCONFIG_LIBDIR ${LIB_INSTALL_DIR}) +ELSE(IS_ABSOLUTE ${LIB_INSTALL_DIR}) + SET (PKGCONFIG_LIBDIR "\${prefix}/${LIB_INSTALL_DIR}") +ENDIF (IS_ABSOLUTE ${LIB_INSTALL_DIR}) + +IF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + SET (PKGCONFIG_INCLUDEDIR ${INCLUDE_INSTALL_DIR}) +ELSE(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + SET (PKGCONFIG_INCLUDEDIR "\${prefix}/${INCLUDE_INSTALL_DIR}") +ENDIF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + +# Enable tracing +IF (ENABLE_TRACE STREQUAL "ON") + SET(GIT_TRACE 1) +ENDIF() +ADD_FEATURE_INFO(tracing GIT_TRACE "tracing support") + +CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) +IF (HAVE_REGCOMP_L) + SET(GIT_USE_REGCOMP_L 1) +ENDIF () + +CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) +IF (HAVE_FUTIMENS) + SET(GIT_USE_FUTIMENS 1) +ENDIF () + +CHECK_FUNCTION_EXISTS(qsort_r HAVE_QSORT_R) +IF (HAVE_QSORT_R) + ADD_DEFINITIONS(-DHAVE_QSORT_R) +ENDIF () + +CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S) +IF (HAVE_QSORT_S) + ADD_DEFINITIONS(-DHAVE_QSORT_S) +ENDIF () + +# Find required dependencies + +IF(WIN32) + LIST(APPEND LIBGIT2_LIBS ws2_32) +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + LIST(APPEND LIBGIT2_LIBS socket nsl) + LIST(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") +ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Haiku") + LIST(APPEND LIBGIT2_LIBS network) + LIST(APPEND LIBGIT2_PC_LIBS "-lnetwork") +ENDIF() + +CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" NEED_LIBRT) +IF(NEED_LIBRT) + LIST(APPEND LIBGIT2_LIBS rt) + LIST(APPEND LIBGIT2_PC_LIBS "-lrt") +ENDIF() + +IF(THREADSAFE) + LIST(APPEND LIBGIT2_LIBS ${CMAKE_THREAD_LIBS_INIT}) + LIST(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) +ENDIF() +ADD_FEATURE_INFO(threadsafe THREADSAFE "threadsafe support") + + +IF (WIN32 AND EMBED_SSH_PATH) + FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") + LIST(APPEND LIBGIT2_INCLUDES "${EMBED_SSH_PATH}/include") + FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") + SET(GIT_SSH 1) +ENDIF() + +IF (WIN32 AND WINHTTP) + SET(GIT_WINHTTP 1) + + # Since MinGW does not come with headers or an import library for winhttp, + # we have to include a private header and generate our own import library + IF (MINGW) + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/winhttp" "${libgit2_BINARY_DIR}/deps/winhttp") + LIST(APPEND LIBGIT2_LIBS winhttp) + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/winhttp") + LIST(APPEND LIBGIT2_LIBDIRS ${LIBWINHTTP_PATH}) + ELSE() + LIST(APPEND LIBGIT2_LIBS "winhttp") + LIST(APPEND LIBGIT2_PC_LIBS "-lwinhttp") + ENDIF () + + LIST(APPEND LIBGIT2_LIBS "rpcrt4" "crypt32" "ole32") + LIST(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") +ELSE () + IF (CURL) + PKG_CHECK_MODULES(CURL libcurl) + ENDIF () + + IF (CURL_FOUND) + SET(GIT_CURL 1) + LIST(APPEND LIBGIT2_INCLUDES ${CURL_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBDIRS ${CURL_LIBRARY_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${CURL_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS ${CURL_LDFLAGS}) + ENDIF() + ADD_FEATURE_INFO(cURL GIT_CURL "cURL for HTTP proxy support") +ENDIF() + +IF (USE_HTTPS) + IF (CMAKE_SYSTEM_NAME MATCHES "Darwin") + FIND_PACKAGE(Security) + FIND_PACKAGE(CoreFoundation) + ENDIF() + + # Auto-select TLS backend + IF (USE_HTTPS STREQUAL ON) + IF (SECURITY_FOUND) + IF (SECURITY_HAS_SSLCREATECONTEXT) + SET(HTTPS_BACKEND "SecureTransport") + ELSE() + MESSAGE("-- Security framework is too old, falling back to OpenSSL") + SET(HTTPS_BACKEND "OpenSSL") + ENDIF() + ELSEIF (WINHTTP) + SET(HTTPS_BACKEND "WinHTTP") + ELSE() + SET(HTTPS_BACKEND "OpenSSL") + ENDIF() + ELSE() + # Backend was explicitly set + SET(HTTPS_BACKEND ${USE_HTTPS}) + ENDIF() + + # Check that we can find what's required for the selected backend + IF (HTTPS_BACKEND STREQUAL "SecureTransport") + IF (NOT COREFOUNDATION_FOUND) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, CoreFoundation.framework not found") + ENDIF() + IF (NOT SECURITY_FOUND) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, Security.framework not found") + ENDIF() + IF (NOT SECURITY_HAS_SSLCREATECONTEXT) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, SSLCreateContext not supported") + ENDIF() + + SET(GIT_SECURE_TRANSPORT 1) + LIST(APPEND LIBGIT2_INCLUDES ${SECURITY_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${COREFOUNDATION_LIBRARIES} ${SECURITY_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS ${COREFOUNDATION_LDFLAGS} ${SECURITY_LDFLAGS}) + ELSEIF (HTTPS_BACKEND STREQUAL "OpenSSL") + FIND_PACKAGE(OpenSSL) + + IF (NOT OPENSSL_FOUND) + MESSAGE(FATAL_ERROR "Asked for OpenSSL TLS backend, but it wasn't found") + ENDIF() + + SET(GIT_OPENSSL 1) + LIST(APPEND LIBGIT2_INCLUDES ${OPENSSL_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${OPENSSL_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) + LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") + ELSEIF (HTTPS_BACKEND STREQUAL "WinHTTP") + # WinHTTP setup was handled in the WinHTTP-specific block above + ELSE() + MESSAGE(FATAL_ERROR "Asked for backend ${HTTPS_BACKEND} but it wasn't found") + ENDIF() + + ADD_FEATURE_INFO(HTTPS ON "using ${HTTPS_BACKEND}") + SET(GIT_HTTPS 1) +ELSE() + ADD_FEATURE_INFO(HTTPS OFF "no support") +ENDIF() + +# Specify sha1 implementation +IF(SHA1_BACKEND STREQUAL "OpenSSL") + IF(NOT OPENSSL_FOUND) + FIND_PACKAGE(OpenSSL) + IF(NOT OPENSSL_FOUND) + MESSAGE(FATAL_ERROR "Requested OpenSSL SHA1 backend, but OpenSSL could not be found") + ENDIF() + ENDIF() + + ADD_FEATURE_INFO(SHA ON "using OpenSSL") + SET(GIT_SHA1_OPENSSL 1) + IF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + LIST(APPEND LIBGIT2_PC_LIBS "-lssl") + ELSE() + LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") + ENDIF() +ELSEIF(SHA1_BACKEND STREQUAL "CollisionDetection") + ADD_FEATURE_INFO(SHA ON "using CollisionDetection") + SET(GIT_SHA1_COLLISIONDETECT 1) + ADD_DEFINITIONS(-DSHA1DC_NO_STANDARD_INCLUDES=1) + ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\") + ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\") + FILE(GLOB SRC_SHA1 hash/hash_collisiondetect.c hash/sha1dc/*) +ELSEIF(SHA1_BACKEND STREQUAL "Generic") + ADD_FEATURE_INFO(SHA ON "using Generic") + FILE(GLOB SRC_SHA1 hash/hash_generic.c) +ELSEIF(SHA1_BACKEND STREQUAL "Win32") + ADD_FEATURE_INFO(SHA ON "using Win32") + SET(GIT_SHA1_WIN32 1) + FILE(GLOB SRC_SHA1 hash/hash_win32.c) +ELSEIF(SHA1_BACKEND STREQUAL "CommonCrypto") + ADD_FEATURE_INFO(SHA ON "using CommonCrypto") + SET(GIT_SHA1_COMMON_CRYPTO 1) +ELSE() + MESSAGE(FATAL_ERROR "Asked for unknown SHA1 backend ${SHA1_BACKEND}") +ENDIF() + +# Include POSIX regex when it is required +IF(WIN32 OR AMIGA OR CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/regex" "${libgit2_BINARY_DIR}/deps/regex") + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/regex") + LIST(APPEND LIBGIT2_OBJECTS $) +ENDIF() + +# Optional external dependency: http-parser +FIND_PACKAGE(HTTP_Parser) +IF (USE_EXT_HTTP_PARSER AND HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) + LIST(APPEND LIBGIT2_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${HTTP_PARSER_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") + ADD_FEATURE_INFO(http-parser ON "http-parser support") +ELSE() + MESSAGE(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.") + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/http-parser" "${libgit2_BINARY_DIR}/deps/http-parser") + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/http-parser") + LIST(APPEND LIBGIT2_OBJECTS "$") + ADD_FEATURE_INFO(http-parser ON "http-parser support (bundled)") +ENDIF() + +# Optional external dependency: zlib +IF(NOT USE_BUNDLED_ZLIB) + FIND_PACKAGE(ZLIB) + IF(ZLIB_FOUND) + LIST(APPEND LIBGIT2_INCLUDES ${ZLIB_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${ZLIB_LIBRARIES}) + IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + LIST(APPEND LIBGIT2_LIBS "z") + LIST(APPEND LIBGIT2_PC_LIBS "-lz") + ELSE() + LIST(APPEND LIBGIT2_PC_REQUIRES "zlib") + ENDIF() + ADD_FEATURE_INFO(zlib ON "using system zlib") + ELSE() + MESSAGE(STATUS "zlib was not found; using bundled 3rd-party sources." ) + ENDIF() +ENDIF() +IF(USE_BUNDLED_ZLIB OR NOT ZLIB_FOUND) + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/zlib" "${libgit2_BINARY_DIR}/deps/zlib") + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/zlib") + LIST(APPEND LIBGIT2_OBJECTS $) + ADD_FEATURE_INFO(zlib ON "using bundled zlib") +ENDIF() + +# Optional external dependency: libssh2 +IF (USE_SSH) + PKG_CHECK_MODULES(LIBSSH2 libssh2) +ENDIF() +IF (LIBSSH2_FOUND) + SET(GIT_SSH 1) + LIST(APPEND LIBGIT2_INCLUDES ${LIBSSH2_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${LIBSSH2_LIBRARIES}) + LIST(APPEND LIBGIT2_LIBDIRS ${LIBSSH2_LIBRARY_DIRS}) + LIST(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS}) + #SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} ${LIBSSH2_LDFLAGS}") + + CHECK_LIBRARY_EXISTS("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS) + IF (HAVE_LIBSSH2_MEMORY_CREDENTIALS) + SET(GIT_SSH_MEMORY_CREDENTIALS 1) + ENDIF() +ELSE() + MESSAGE(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.") +ENDIF() +ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support") + +# Optional external dependency: libgssapi +IF (USE_GSSAPI) + FIND_PACKAGE(GSSAPI) +ENDIF() +IF (GSSAPI_FOUND) + SET(GIT_GSSAPI 1) + LIST(APPEND LIBGIT2_LIBS ${GSSAPI_LIBRARIES}) +ENDIF() +ADD_FEATURE_INFO(SPNEGO GIT_GSSAPI "SPNEGO authentication support") + +# Optional external dependency: iconv +IF (USE_ICONV) + FIND_PACKAGE(Iconv) +ENDIF() +IF (ICONV_FOUND) + SET(GIT_USE_ICONV 1) + LIST(APPEND LIBGIT2_INCLUDES ${ICONV_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${ICONV_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) +ENDIF() +ADD_FEATURE_INFO(iconv GIT_USE_ICONV "iconv encoding conversion support") + + +IF (THREADSAFE) + IF (NOT WIN32) + FIND_PACKAGE(Threads REQUIRED) + ENDIF() + + SET(GIT_THREADS 1) +ENDIF() + +IF (USE_NSEC) + SET(GIT_USE_NSEC 1) +ENDIF() + +IF (HAVE_STRUCT_STAT_ST_MTIM) + SET(GIT_USE_STAT_MTIM 1) +ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC) + SET(GIT_USE_STAT_MTIMESPEC 1) +ELSEIF (HAVE_STRUCT_STAT_ST_MTIME_NSEC) + SET(GIT_USE_STAT_MTIME_NSEC 1) +ENDIF() + +ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) + +# Collect sourcefiles +FILE(GLOB SRC_H + "${libgit2_SOURCE_DIR}/include/git2.h" + "${libgit2_SOURCE_DIR}/include/git2/*.h" + "${libgit2_SOURCE_DIR}/include/git2/sys/*.h") + +# On Windows use specific platform sources +IF (WIN32 AND NOT CYGWIN) + ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) + + IF(MSVC) + SET(WIN_RC "win32/git2.rc") + ENDIF() + + FILE(GLOB SRC_OS win32/*.c win32/*.h) +ELSEIF (AMIGA) + ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) +ELSE() + IF (VALGRIND) + ADD_DEFINITIONS(-DNO_MMAP) + ENDIF() + FILE(GLOB SRC_OS unix/*.c unix/*.h) +ENDIF() +FILE(GLOB SRC_GIT2 *.c *.h + streams/*.c streams/*.h + transports/*.c transports/*.h + xdiff/*.c xdiff/*.h) + +# Determine architecture of the machine +IF (CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(GIT_ARCH_64 1) +ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4) + SET(GIT_ARCH_32 1) +ELSEIF (CMAKE_SIZEOF_VOID_P) + MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") +ELSE() + MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") +ENDIF() + +CONFIGURE_FILE(features.h.in git2/sys/features.h) + +SET(LIBGIT2_SOURCES ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_SSH} ${SRC_SHA1}) + +ADD_LIBRARY(git2internal OBJECT ${LIBGIT2_SOURCES}) +IDE_SPLIT_SOURCES(git2internal) +LIST(APPEND LIBGIT2_OBJECTS $) + +IF (${CMAKE_VERSION} VERSION_LESS 2.8.12) + INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES}) +ELSE() + TARGET_INCLUDE_DIRECTORIES(git2internal + PRIVATE ${LIBGIT2_INCLUDES} + PUBLIC ${libgit2_SOURCE_DIR}/include) +ENDIF() + +SET(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) +SET(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) +SET(LIBGIT2_LIBS ${LIBGIT2_LIBS} PARENT_SCOPE) +SET(LIBGIT2_LIBDIRS ${LIBGIT2_LIBDIRS} PARENT_SCOPE) + +IF(XCODE_VERSION) + # This is required for Xcode to actually link the libgit2 library + # when using only object libraries. + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "") + LIST(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c) +ENDIF() + +# Compile and link libgit2 +LINK_DIRECTORIES(${LIBGIT2_LIBDIRS}) +ADD_LIBRARY(git2 ${WIN_RC} ${LIBGIT2_OBJECTS}) +TARGET_LINK_LIBRARIES(git2 ${LIBGIT2_LIBS}) + +SET_TARGET_PROPERTIES(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) +SET_TARGET_PROPERTIES(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) +SET_TARGET_PROPERTIES(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) + +# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) +# Win64+MSVC+static libs = linker error +IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) + SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") +ENDIF() + +IDE_SPLIT_SOURCES(git2) + +IF (SONAME) + SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) + SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION}) + IF (LIBGIT2_FILENAME) + ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") + SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) + ELSEIF (DEFINED LIBGIT2_PREFIX) + SET_TARGET_PROPERTIES(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") + ENDIF() +ENDIF() + +LIST(REMOVE_DUPLICATES LIBGIT2_PC_REQUIRES) +STRING(REPLACE ";" " " LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES}") +STRING(REPLACE ";" " " LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS}") +CONFIGURE_FILE(${libgit2_SOURCE_DIR}/libgit2.pc.in ${libgit2_BINARY_DIR}/libgit2.pc @ONLY) + +IF (MSVC_IDE) + # Precompiled headers + SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + SET_SOURCE_FILES_PROPERTIES(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") +ENDIF () + +# Install +INSTALL(TARGETS git2 + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} +) +INSTALL(FILES ${libgit2_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) +INSTALL(DIRECTORY ${libgit2_SOURCE_DIR}/include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} ) +INSTALL(FILES ${libgit2_SOURCE_DIR}/include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) diff --git a/src/annotated_commit.c b/src/annotated_commit.c index c2c770cba..72ba80a22 100644 --- a/src/annotated_commit.c +++ b/src/annotated_commit.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "annotated_commit.h" + #include "refs.h" #include "cache.h" diff --git a/src/annotated_commit.h b/src/annotated_commit.h index 3ac8b5f69..b390066b2 100644 --- a/src/annotated_commit.h +++ b/src/annotated_commit.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_annotated_commit_h__ #define INCLUDE_annotated_commit_h__ +#include "common.h" + #include "oidarray.h" #include "git2/oid.h" diff --git a/src/apply.c b/src/apply.c index 595f5f300..7801a0a54 100644 --- a/src/apply.c +++ b/src/apply.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "apply.h" + #include #include "git2/patch.h" @@ -12,7 +14,6 @@ #include "array.h" #include "patch.h" #include "fileops.h" -#include "apply.h" #include "delta.h" #include "zstream.h" diff --git a/src/apply.h b/src/apply.h index 96e0f55b5..b29460c0b 100644 --- a/src/apply.h +++ b/src/apply.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_apply_h__ #define INCLUDE_apply_h__ +#include "common.h" + #include "git2/patch.h" #include "buffer.h" diff --git a/src/attr.c b/src/attr.c index 999f41318..93d9551d9 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,4 +1,12 @@ -#include "common.h" +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "attr.h" + #include "repository.h" #include "sysdir.h" #include "config.h" @@ -48,12 +56,16 @@ int git_attr_get( git_attr_file *file; git_attr_name attr; git_attr_rule *rule; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; assert(value && repo && name); *value = NULL; - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0) + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) return -1; if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0) @@ -106,13 +118,17 @@ int git_attr_get_many_with_session( git_attr_rule *rule; attr_get_many_info *info = NULL; size_t num_found = 0; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; if (!num_attr) return 0; assert(values && repo && names); - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0) + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) return -1; if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0) @@ -188,10 +204,14 @@ int git_attr_foreach( git_attr_rule *rule; git_attr_assignment *assign; git_strmap *seen = NULL; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; assert(repo && callback); - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0) + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) return -1; if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 || diff --git a/src/attr.h b/src/attr.h index f9f216d07..977565205 100644 --- a/src/attr.h +++ b/src/attr.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_attr_h__ #define INCLUDE_attr_h__ +#include "common.h" + #include "attr_file.h" #include "attrcache.h" diff --git a/src/attr_file.c b/src/attr_file.c index e30ea5e0c..55d0c3865 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -1,7 +1,14 @@ -#include "common.h" +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "attr_file.h" + #include "repository.h" #include "filebuf.h" -#include "attr_file.h" #include "attrcache.h" #include "git2/blob.h" #include "git2/tree.h" diff --git a/src/attr_file.h b/src/attr_file.h index a9af2403a..fedf55af5 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_attr_file_h__ #define INCLUDE_attr_file_h__ +#include "common.h" + #include "git2/oid.h" #include "git2/attr.h" #include "vector.h" diff --git a/src/attrcache.c b/src/attrcache.c index 54161894b..65e7b4655 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -1,4 +1,12 @@ -#include "common.h" +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "attrcache.h" + #include "repository.h" #include "attr_file.h" #include "config.h" diff --git a/src/attrcache.h b/src/attrcache.h index b91edd3e8..f528911ea 100644 --- a/src/attrcache.h +++ b/src/attrcache.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_attrcache_h__ #define INCLUDE_attrcache_h__ +#include "common.h" + #include "attr_file.h" #include "strmap.h" diff --git a/src/blame.c b/src/blame.c index 2c8584ba5..a923bf003 100644 --- a/src/blame.c +++ b/src/blame.c @@ -6,6 +6,7 @@ */ #include "blame.h" + #include "git2/commit.h" #include "git2/revparse.h" #include "git2/revwalk.h" diff --git a/src/blame.h b/src/blame.h index d8db8d5c1..8fd3ee5b1 100644 --- a/src/blame.h +++ b/src/blame.h @@ -1,8 +1,9 @@ #ifndef INCLUDE_blame_h__ #define INCLUDE_blame_h__ -#include "git2/blame.h" #include "common.h" + +#include "git2/blame.h" #include "vector.h" #include "diff.h" #include "array.h" diff --git a/src/blame_git.c b/src/blame_git.c index 13f5cb47c..3c221b318 100644 --- a/src/blame_git.c +++ b/src/blame_git.c @@ -6,6 +6,7 @@ */ #include "blame_git.h" + #include "commit.h" #include "blob.h" #include "xdiff/xinclude.h" diff --git a/src/blame_git.h b/src/blame_git.h index 1891b0e1f..48b85a20d 100644 --- a/src/blame_git.h +++ b/src/blame_git.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_blame_git__ #define INCLUDE_blame_git__ +#include "common.h" + #include "blame.h" int git_blame__get_origin( diff --git a/src/blob.c b/src/blob.c index 19d3039fb..3396fe74f 100644 --- a/src/blob.c +++ b/src/blob.c @@ -5,14 +5,14 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "blob.h" + #include "git2/common.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/odb_backend.h" -#include "common.h" #include "filebuf.h" -#include "blob.h" #include "filter.h" #include "buf_text.h" diff --git a/src/blob.h b/src/blob.h index 4cd9f1e0c..3f1f97719 100644 --- a/src/blob.h +++ b/src/blob.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_blob_h__ #define INCLUDE_blob_h__ +#include "common.h" + #include "git2/blob.h" #include "repository.h" #include "odb.h" diff --git a/src/branch.c b/src/branch.c index fe4955ad6..42addc2ce 100644 --- a/src/branch.c +++ b/src/branch.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "branch.h" + #include "commit.h" #include "tag.h" #include "config.h" @@ -69,6 +70,12 @@ static int create_branch( assert(branch_name && commit && ref_out); assert(git_object_owner((const git_object *)commit) == repository); + if (!git__strcmp(branch_name, "HEAD")) { + giterr_set(GITERR_REFERENCE, "'HEAD' is not a valid branch name"); + error = -1; + goto cleanup; + } + if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { error = git_branch_is_head(branch); git_reference_free(branch); diff --git a/src/branch.h b/src/branch.h index d02f2af0d..5ae227c05 100644 --- a/src/branch.h +++ b/src/branch.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_branch_h__ #define INCLUDE_branch_h__ +#include "common.h" + #include "buffer.h" int git_branch_upstream__name( diff --git a/src/buf_text.c b/src/buf_text.c index 7e6779d2d..306980b5c 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -188,7 +188,7 @@ bool git_buf_text_is_binary(const git_buf *buf) git_bom_t bom; int printable = 0, nonprintable = 0; - scan += git_buf_text_detect_bom(&bom, buf, 0); + scan += git_buf_text_detect_bom(&bom, buf); if (bom > GIT_BOM_UTF8) return 1; @@ -215,18 +215,18 @@ bool git_buf_text_contains_nul(const git_buf *buf) return (memchr(buf->ptr, '\0', buf->size) != NULL); } -int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf, size_t offset) +int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf) { const char *ptr; size_t len; *bom = GIT_BOM_NONE; - /* need at least 2 bytes after offset to look for any BOM */ - if (buf->size < offset + 2) + /* need at least 2 bytes to look for any BOM */ + if (buf->size < 2) return 0; - ptr = buf->ptr + offset; - len = buf->size - offset; + ptr = buf->ptr; + len = buf->size; switch (*ptr++) { case 0: @@ -274,7 +274,7 @@ bool git_buf_text_gather_stats( memset(stats, 0, sizeof(*stats)); /* BOM detection */ - skip = git_buf_text_detect_bom(&stats->bom, buf, 0); + skip = git_buf_text_detect_bom(&stats->bom, buf); if (skip_bom) scan += skip; diff --git a/src/buf_text.h b/src/buf_text.h index c9c55af89..726b0ae7b 100644 --- a/src/buf_text.h +++ b/src/buf_text.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_buf_text_h__ #define INCLUDE_buf_text_h__ +#include "common.h" + #include "buffer.h" typedef enum { @@ -97,11 +99,9 @@ extern bool git_buf_text_contains_nul(const git_buf *buf); * * @param bom Set to the type of BOM detected or GIT_BOM_NONE * @param buf Buffer in which to check the first bytes for a BOM - * @param offset Offset into buffer to look for BOM * @return Number of bytes of BOM data (or 0 if no BOM found) */ -extern int git_buf_text_detect_bom( - git_bom_t *bom, const git_buf *buf, size_t offset); +extern int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf); /** * Gather stats for a piece of text diff --git a/src/buffer.c b/src/buffer.c index 6dfcbfbe6..8a58d1afa 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -212,7 +212,7 @@ int git_buf_put(git_buf *buf, const char *data, size_t len) size_t new_size; assert(data); - + GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len); GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1); ENSURE_SIZE(buf, new_size); @@ -455,6 +455,36 @@ on_error: return -1; } +#define HEX_DECODE(c) ((c | 32) % 39 - 9) + +int git_buf_decode_percent( + git_buf *buf, + const char *str, + size_t str_len) +{ + size_t str_pos, new_size; + + GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); + GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { + if (str[str_pos] == '%' && + str_len > str_pos + 2 && + isxdigit(str[str_pos + 1]) && + isxdigit(str[str_pos + 2])) { + buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + + HEX_DECODE(str[str_pos + 2]); + str_pos += 2; + } else { + buf->ptr[buf->size] = str[str_pos]; + } + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) { size_t expected_size, new_size; diff --git a/src/buffer.h b/src/buffer.h index b0aece488..cc77fc030 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -190,6 +190,9 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len); /* Decode the given "base85" and write the result to the buffer */ int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len); +/* Decode the given percent-encoded string and write the result to the buffer */ +int git_buf_decode_percent(git_buf *buf, const char *str, size_t len); + /* * Insert, remove or replace a portion of the buffer. * diff --git a/src/cache.c b/src/cache.c index c92a3a78a..cdd12979f 100644 --- a/src/cache.c +++ b/src/cache.c @@ -5,12 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "cache.h" + #include "repository.h" #include "commit.h" #include "thread-utils.h" #include "util.h" -#include "cache.h" #include "odb.h" #include "object.h" #include "git2/oid.h" diff --git a/src/cache.h b/src/cache.h index 0f0bfcf5d..9c09954ae 100644 --- a/src/cache.h +++ b/src/cache.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_cache_h__ #define INCLUDE_cache_h__ +#include "common.h" + #include "git2/common.h" #include "git2/oid.h" #include "git2/odb.h" diff --git a/src/cc-compat.h b/src/cc-compat.h index cefdc928b..0f05cd2d9 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_compat_h__ -#define INCLUDE_compat_h__ +#ifndef INCLUDE_cc_compat_h__ +#define INCLUDE_cc_compat_h__ #include @@ -84,4 +84,4 @@ # endif #endif -#endif /* INCLUDE_compat_h__ */ +#endif diff --git a/src/checkout.c b/src/checkout.c index 25018d291..8ff5d897b 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -5,10 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include - #include "checkout.h" +#include + #include "git2/repository.h" #include "git2/refs.h" #include "git2/tree.h" @@ -70,6 +70,7 @@ typedef struct { git_buf tmp; unsigned int strategy; int can_symlink; + int respect_filemode; bool reload_submodules; size_t total_steps; size_t completed_steps; @@ -159,6 +160,22 @@ GIT_INLINE(bool) is_workdir_base_or_new( git_oid__cmp(&newitem->id, workdir_id) == 0); } +GIT_INLINE(bool) is_filemode_changed(git_filemode_t a, git_filemode_t b, int respect_filemode) +{ + /* If core.filemode = false, ignore links in the repository and executable bit changes */ + if (!respect_filemode) { + if (a == S_IFLNK) + a = GIT_FILEMODE_BLOB; + if (b == S_IFLNK) + b = GIT_FILEMODE_BLOB; + + a &= ~0111; + b &= ~0111; + } + + return (a != b); +} + static bool checkout_is_workdir_modified( checkout_data *data, const git_diff_file *baseitem, @@ -192,16 +209,23 @@ static bool checkout_is_workdir_modified( return rval; } - /* Look at the cache to decide if the workdir is modified. If not, - * we can simply compare the oid in the cache to the baseitem instead - * of hashing the file. If so, we allow the checkout to proceed if the - * oid is identical (ie, the staged item is what we're trying to check - * out.) + /* + * Look at the cache to decide if the workdir is modified: if the + * cache contents match the workdir contents, then we do not need + * to examine the working directory directly, instead we can + * examine the cache to see if _it_ has been modified. This allows + * us to avoid touching the disk. */ - if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) { - if (git_index_time_eq(&wditem->mtime, &ie->mtime) && - wditem->file_size == ie->file_size) - return !is_workdir_base_or_new(&ie->id, baseitem, newitem); + ie = git_index_get_bypath(data->index, wditem->path, 0); + + if (ie != NULL && + git_index_time_eq(&wditem->mtime, &ie->mtime) && + wditem->file_size == ie->file_size && + !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { + + /* The workdir is modified iff the index entry is modified */ + return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || + is_filemode_changed(baseitem->mode, ie->mode, data->respect_filemode); } /* depending on where base is coming from, we may or may not know @@ -214,6 +238,9 @@ static bool checkout_is_workdir_modified( if (S_ISDIR(wditem->mode)) return false; + if (is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode)) + return true; + if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) return false; @@ -2005,8 +2032,11 @@ static int checkout_write_entry( (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) return error; - return checkout_write_content(data, - &side->id, fullpath->ptr, hint_path, side->mode, &st); + if (!S_ISGITLINK(side->mode)) + return checkout_write_content(data, + &side->id, fullpath->ptr, hint_path, side->mode, &st); + + return 0; } static int checkout_write_entries( @@ -2428,6 +2458,10 @@ static int checkout_data_init( &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0) goto cleanup; + if ((error = git_repository__cvar( + &data->respect_filemode, repo, GIT_CVAR_FILEMODE)) < 0) + goto cleanup; + if (!data->opts.baseline && !data->opts.baseline_index) { data->opts_free_baseline = true; error = 0; diff --git a/src/checkout.h b/src/checkout.h index 60aa29b26..517fbf3b1 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_checkout_h__ #define INCLUDE_checkout_h__ +#include "common.h" + #include "git2/checkout.h" #include "iterator.h" diff --git a/src/cherrypick.c b/src/cherrypick.c index d8b6858ae..e42b74815 100644 --- a/src/cherrypick.c +++ b/src/cherrypick.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "repository.h" #include "filebuf.h" #include "merge.h" diff --git a/src/clone.c b/src/clone.c index 16ddface2..8764bb728 100644 --- a/src/clone.c +++ b/src/clone.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "clone.h" + #include #include "git2/clone.h" @@ -16,7 +18,6 @@ #include "git2/commit.h" #include "git2/tree.h" -#include "common.h" #include "remote.h" #include "fileops.h" #include "refs.h" diff --git a/src/clone.h b/src/clone.h index 14ca5d44c..864b59029 100644 --- a/src/clone.h +++ b/src/clone.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_clone_h__ #define INCLUDE_clone_h__ +#include "common.h" + +#include "git2/clone.h" + extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); #endif diff --git a/src/commit.c b/src/commit.c index 4a340058a..838688bb8 100644 --- a/src/commit.c +++ b/src/commit.c @@ -5,13 +5,14 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "commit.h" + #include "git2/common.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/signature.h" #include "git2/sys/commit.h" -#include "common.h" #include "odb.h" #include "commit.h" #include "signature.h" diff --git a/src/commit.h b/src/commit.h index d01ac2b2f..781809d70 100644 --- a/src/commit.h +++ b/src/commit.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_commit_h__ #define INCLUDE_commit_h__ +#include "common.h" + #include "git2/commit.h" #include "tree.h" #include "repository.h" diff --git a/src/commit_list.c b/src/commit_list.c index 3bba58c27..96bd9dc15 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -6,7 +6,7 @@ */ #include "commit_list.h" -#include "common.h" + #include "revwalk.h" #include "pool.h" #include "odb.h" diff --git a/src/commit_list.h b/src/commit_list.h index 9746c2801..a7551a2bc 100644 --- a/src/commit_list.h +++ b/src/commit_list.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_commit_list_h__ #define INCLUDE_commit_list_h__ +#include "common.h" + #include "git2/oid.h" #define PARENT1 (1 << 0) diff --git a/src/common.h b/src/common.h index e566aeabd..44063be12 100644 --- a/src/common.h +++ b/src/common.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_common_h__ #define INCLUDE_common_h__ +#ifndef LIBGIT2_NO_FEATURES_H +# include "git2/sys/features.h" +#endif + #include "git2/common.h" #include "cc-compat.h" @@ -47,10 +51,6 @@ # ifdef GIT_THREADS # include "win32/thread.h" # endif -# if defined(GIT_MSVC_CRTDBG) -# include "win32/w32_stack.h" -# include "win32/w32_crtdbg_stacktrace.h" -# endif #else @@ -230,6 +230,12 @@ GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int v GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } +#define GITERR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } + /** Check for multiplicative overflow, failing if it would occur. */ #define GITERR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } @@ -238,4 +244,4 @@ GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int v #include "util.h" -#endif /* INCLUDE_common_h__ */ +#endif diff --git a/src/config.c b/src/config.c index 169a62880..1bc11b99f 100644 --- a/src/config.c +++ b/src/config.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "sysdir.h" #include "config.h" + +#include "sysdir.h" #include "git2/config.h" #include "git2/sys/config.h" #include "vector.h" @@ -99,6 +99,7 @@ int git_config_add_file_ondisk( git_config *cfg, const char *path, git_config_level_t level, + const git_repository *repo, int force) { git_config_backend *file = NULL; @@ -116,7 +117,7 @@ int git_config_add_file_ondisk( if (git_config_file__ondisk(&file, path) < 0) return -1; - if ((res = git_config_add_backend(cfg, file, level, force)) < 0) { + if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup @@ -138,7 +139,7 @@ int git_config_open_ondisk(git_config **out, const char *path) if (git_config_new(&config) < 0) return -1; - if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0) + if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)) < 0) git_config_free(config); else *out = config; @@ -164,7 +165,7 @@ int git_config_snapshot(git_config **out, git_config *in) if ((error = internal->file->snapshot(&b, internal->file)) < 0) break; - if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) { + if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { b->free(b); break; } @@ -307,6 +308,7 @@ int git_config_add_backend( git_config *cfg, git_config_backend *file, git_config_level_t level, + const git_repository *repo, int force) { file_internal *internal; @@ -316,7 +318,7 @@ int git_config_add_backend( GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); - if ((result = file->open(file, level)) < 0) + if ((result = file->open(file, level, repo)) < 0) return result; internal = git__malloc(sizeof(file_internal)); @@ -1147,20 +1149,20 @@ int git_config_open_default(git_config **out) if (!git_config_find_global(&buf) || !git_config__global_location(&buf)) { error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_GLOBAL, 0); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); } if (!error && !git_config_find_xdg(&buf)) error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_XDG, 0); + GIT_CONFIG_LEVEL_XDG, NULL, 0); if (!error && !git_config_find_system(&buf)) error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_SYSTEM, 0); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); if (!error && !git_config_find_programdata(&buf)) error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_PROGRAMDATA, 0); + GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); git_buf_free(&buf); diff --git a/src/config.h b/src/config.h index 00c12b50d..a5fcf2e84 100644 --- a/src/config.h +++ b/src/config.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_config_h__ #define INCLUDE_config_h__ +#include "common.h" + #include "git2.h" #include "git2/config.h" #include "vector.h" diff --git a/src/config_cache.c b/src/config_cache.c index 840722274..0efb1a789 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "fileops.h" #include "repository.h" #include "config.h" diff --git a/src/config_file.c b/src/config_file.c index e15d57bbb..aa9e83b9c 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "config_file.h" + #include "config.h" #include "filebuf.h" #include "sysdir.h" @@ -16,6 +17,7 @@ #include "git2/types.h" #include "strmap.h" #include "array.h" +#include "config_parse.h" #include #include @@ -74,15 +76,6 @@ typedef struct git_config_file_iter { (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ (iter) = (tmp)) -struct reader { - git_oid checksum; - char *file_path; - git_buf buffer; - char *read_ptr; - int line_number; - int eof; -}; - typedef struct { git_atomic refcount; git_strmap *values; @@ -93,20 +86,20 @@ typedef struct { /* mutex to coordinate accessing the values */ git_mutex values_mutex; refcounted_strmap *values; + const git_repository *repo; + git_config_level_t level; } diskfile_header; typedef struct { diskfile_header header; - git_config_level_t level; - - git_array_t(struct reader) readers; + git_array_t(git_config_parser) readers; bool locked; git_filebuf locked_buf; git_buf locked_content; - char *file_path; + struct config_file file; } diskfile_backend; typedef struct { @@ -115,19 +108,13 @@ typedef struct { diskfile_backend *snapshot_from; } diskfile_readonly_backend; -static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth); -static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); +static int config_read(git_strmap *values, const git_repository *repo, git_config_file *file, git_config_level_t level, int depth); +static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char *value); static char *escape_value(const char *ptr); int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in); static int config_snapshot(git_config_backend **out, git_config_backend *in); -static void set_parse_error(struct reader *reader, int col, const char *error_str) -{ - giterr_set(GITERR_CONFIG, "failed to parse config file: %s (in %s:%d, column %d)", - error_str, reader->file_path, reader->line_number, col); -} - static int config_error_readonly(void) { giterr_set(GITERR_CONFIG, "this backend is read-only"); @@ -261,62 +248,104 @@ static int refcounted_strmap_alloc(refcounted_strmap **out) return error; } -static int config_open(git_config_backend *cfg, git_config_level_t level) +static void config_file_clear(struct config_file *file) +{ + struct config_file *include; + uint32_t i; + + if (file == NULL) + return; + + git_array_foreach(file->includes, i, include) { + config_file_clear(include); + } + git_array_clear(file->includes); + + git__free(file->path); +} + +static int config_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) { int res; - struct reader *reader; diskfile_backend *b = (diskfile_backend *)cfg; - b->level = level; + b->header.level = level; + b->header.repo = repo; if ((res = refcounted_strmap_alloc(&b->header.values)) < 0) return res; - git_array_init(b->readers); - reader = git_array_alloc(b->readers); - if (!reader) { - refcounted_strmap_free(b->header.values); - return -1; - } - memset(reader, 0, sizeof(struct reader)); - - reader->file_path = git__strdup(b->file_path); - GITERR_CHECK_ALLOC(reader->file_path); - - git_buf_init(&reader->buffer, 0); - res = git_futils_readbuffer_updated( - &reader->buffer, b->file_path, &reader->checksum, NULL); - - /* It's fine if the file doesn't exist */ - if (res == GIT_ENOTFOUND) + if (!git_path_exists(b->file.path)) return 0; - if (res < 0 || (res = config_read(b->header.values->values, b, reader, level, 0)) < 0) { + if (res < 0 || (res = config_read(b->header.values->values, repo, &b->file, level, 0)) < 0) { refcounted_strmap_free(b->header.values); b->header.values = NULL; } - reader = git_array_get(b->readers, 0); - git_buf_free(&reader->buffer); - return res; } -/* The meat of the refresh, as we want to use it in different places */ -static int config__refresh(git_config_backend *cfg) +static int config_is_modified(int *modified, struct config_file *file) { - refcounted_strmap *values = NULL, *tmp; - diskfile_backend *b = (diskfile_backend *)cfg; - struct reader *reader = NULL; + git_config_file *include; + git_buf buf = GIT_BUF_INIT; + git_oid hash; + uint32_t i; int error = 0; + *modified = 0; + + if ((error = git_futils_readbuffer(&buf, file->path)) < 0) + goto out; + + if ((error = git_hash_buf(&hash, buf.ptr, buf.size)) < 0) + goto out; + + if (!git_oid_equal(&hash, &file->checksum)) { + *modified = 1; + goto out; + } + + git_array_foreach(file->includes, i, include) { + if ((error = config_is_modified(modified, include)) < 0 || *modified) + goto out; + } + +out: + git_buf_free(&buf); + + return error; +} + +static int config_refresh(git_config_backend *cfg) +{ + diskfile_backend *b = (diskfile_backend *)cfg; + refcounted_strmap *values = NULL, *tmp; + git_config_file *include; + int error, modified; + uint32_t i; + + if (b->header.parent.readonly) + return config_error_readonly(); + + error = config_is_modified(&modified, &b->file); + if (error < 0 && error != GIT_ENOTFOUND) + goto out; + + if (!modified) + return 0; + if ((error = refcounted_strmap_alloc(&values)) < 0) goto out; - reader = git_array_get(b->readers, git_array_size(b->readers) - 1); - GITERR_CHECK_ALLOC(reader); + /* Reparse the current configuration */ + git_array_foreach(b->file.includes, i, include) { + config_file_clear(include); + } + git_array_clear(b->file.includes); - if ((error = config_read(values->values, b, reader, b->level, 0)) < 0) + if ((error = config_read(values->values, b->header.repo, &b->file, b->header.level, 0)) < 0) goto out; if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) { @@ -332,52 +361,18 @@ static int config__refresh(git_config_backend *cfg) out: refcounted_strmap_free(values); - if (reader) - git_buf_free(&reader->buffer); - return error; -} -static int config_refresh(git_config_backend *cfg) -{ - int error = 0, updated = 0, any_updated = 0; - diskfile_backend *b = (diskfile_backend *)cfg; - struct reader *reader = NULL; - uint32_t i; - - for (i = 0; i < git_array_size(b->readers); i++) { - reader = git_array_get(b->readers, i); - error = git_futils_readbuffer_updated( - &reader->buffer, reader->file_path, - &reader->checksum, &updated); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - if (updated) - any_updated = 1; - } - - if (!any_updated) - return (error == GIT_ENOTFOUND) ? 0 : error; - - return config__refresh(cfg); + return (error == GIT_ENOTFOUND) ? 0 : error; } static void backend_free(git_config_backend *_backend) { diskfile_backend *backend = (diskfile_backend *)_backend; - uint32_t i; if (backend == NULL) return; - for (i = 0; i < git_array_size(backend->readers); i++) { - struct reader *r = git_array_get(backend->readers, i); - git__free(r->file_path); - } - git_array_clear(backend->readers); - - git__free(backend->file_path); + config_file_clear(&backend->file); refcounted_strmap_free(backend->header.values); git_mutex_free(&backend->header.values_mutex); git__free(backend); @@ -424,13 +419,13 @@ static int config_iterator_new( diskfile_header *h; git_config_file_iter *it; git_config_backend *snapshot; - diskfile_backend *b = (diskfile_backend *) backend; + diskfile_header *bh = (diskfile_header *) backend; int error; if ((error = config_snapshot(&snapshot, backend)) < 0) return error; - if ((error = snapshot->open(snapshot, b->level)) < 0) + if ((error = snapshot->open(snapshot, bh->level, bh->repo)) < 0) return error; it = git__calloc(1, sizeof(git_config_file_iter)); @@ -482,6 +477,12 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val goto out; } + if (existing->included) { + giterr_set(GITERR_CONFIG, "modifying included variable is not supported"); + ret = -1; + goto out; + } + /* don't update if old and new values already match */ if ((!existing->entry->value && !value) || (existing->entry->value && value && @@ -498,7 +499,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val GITERR_CHECK_ALLOC(esc_value); } - if ((ret = config_write(b, key, NULL, esc_value)) < 0) + if ((ret = config_write(b, name, key, NULL, esc_value)) < 0) goto out; ret = config_refresh(cfg); @@ -576,7 +577,7 @@ static int config_set_multivar( } /* If we do have it, set call config_write() and reload */ - if ((result = config_write(b, key, &preg, value)) < 0) + if ((result = config_write(b, name, key, &preg, value)) < 0) goto out; result = config_refresh(cfg); @@ -616,12 +617,17 @@ static int config_delete(git_config_backend *cfg, const char *name) var = git_strmap_value_at(values, pos); refcounted_strmap_free(map); + if (var->included) { + giterr_set(GITERR_CONFIG, "cannot delete included variable"); + return -1; + } + if (var->next != NULL) { giterr_set(GITERR_CONFIG, "cannot delete multivar with a single delete"); return -1; } - if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0) + if ((result = config_write(b, name, var->entry->name, NULL, NULL)) < 0) return result; return config_refresh(cfg); @@ -662,7 +668,7 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con goto out; } - if ((result = config_write(b, key, &preg, NULL)) < 0) + if ((result = config_write(b, name, key, &preg, NULL)) < 0) goto out; result = config_refresh(cfg); @@ -685,10 +691,10 @@ static int config_lock(git_config_backend *_cfg) diskfile_backend *cfg = (diskfile_backend *) _cfg; int error; - if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) + if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0) return error; - error = git_futils_readbuffer(&cfg->locked_content, cfg->file_path); + error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); if (error < 0 && error != GIT_ENOTFOUND) { git_filebuf_cleanup(&cfg->locked_buf); return error; @@ -726,8 +732,9 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; git_mutex_init(&backend->header.values_mutex); - backend->file_path = git__strdup(path); - GITERR_CHECK_ALLOC(backend->file_path); + backend->file.path = git__strdup(path); + GITERR_CHECK_ALLOC(backend->file.path); + git_array_init(backend->file.includes); backend->header.parent.open = config_open; backend->header.parent.get = config_get; @@ -810,7 +817,7 @@ static void backend_readonly_free(git_config_backend *_backend) git__free(backend); } -static int config_readonly_open(git_config_backend *cfg, git_config_level_t level) +static int config_readonly_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) { diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg; diskfile_backend *src = b->snapshot_from; @@ -821,8 +828,9 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0) return error; - /* We're just copying data, don't care about the level */ + /* We're just copying data, don't care about the level or repo*/ GIT_UNUSED(level); + GIT_UNUSED(repo); if ((src_map = refcounted_strmap_take(src_header)) == NULL) return -1; @@ -861,397 +869,6 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) return 0; } -static int reader_getchar_raw(struct reader *reader) -{ - int c; - - c = *reader->read_ptr++; - - /* - Win 32 line breaks: if we find a \r\n sequence, - return only the \n as a newline - */ - if (c == '\r' && *reader->read_ptr == '\n') { - reader->read_ptr++; - c = '\n'; - } - - if (c == '\n') - reader->line_number++; - - if (c == 0) { - reader->eof = 1; - c = '\0'; - } - - return c; -} - -#define SKIP_WHITESPACE (1 << 1) -#define SKIP_COMMENTS (1 << 2) - -static int reader_getchar(struct reader *reader, int flags) -{ - const int skip_whitespace = (flags & SKIP_WHITESPACE); - const int skip_comments = (flags & SKIP_COMMENTS); - int c; - - assert(reader->read_ptr); - - do { - c = reader_getchar_raw(reader); - } while (c != '\n' && c != '\0' && skip_whitespace && git__isspace(c)); - - if (skip_comments && (c == '#' || c == ';')) { - do { - c = reader_getchar_raw(reader); - } while (c != '\n' && c != '\0'); - } - - return c; -} - -/* - * Read the next char, but don't move the reading pointer. - */ -static int reader_peek(struct reader *reader, int flags) -{ - void *old_read_ptr; - int old_lineno, old_eof; - int ret; - - assert(reader->read_ptr); - - old_read_ptr = reader->read_ptr; - old_lineno = reader->line_number; - old_eof = reader->eof; - - ret = reader_getchar(reader, flags); - - reader->read_ptr = old_read_ptr; - reader->line_number = old_lineno; - reader->eof = old_eof; - - return ret; -} - -/* - * Read and consume a line, returning it in newly-allocated memory. - */ -static char *reader_readline(struct reader *reader, bool skip_whitespace) -{ - char *line = NULL; - char *line_src, *line_end; - size_t line_len, alloc_len; - - line_src = reader->read_ptr; - - if (skip_whitespace) { - /* Skip empty empty lines */ - while (git__isspace(*line_src)) - ++line_src; - } - - line_end = strchr(line_src, '\n'); - - /* no newline at EOF */ - if (line_end == NULL) - line_end = strchr(line_src, 0); - - line_len = line_end - line_src; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, line_len, 1) || - (line = git__malloc(alloc_len)) == NULL) { - return NULL; - } - - memcpy(line, line_src, line_len); - - do line[line_len] = '\0'; - while (line_len-- > 0 && git__isspace(line[line_len])); - - if (*line_end == '\n') - line_end++; - - if (*line_end == '\0') - reader->eof = 1; - - reader->line_number++; - reader->read_ptr = line_end; - - return line; -} - -/* - * Consume a line, without storing it anywhere - */ -static void reader_consume_line(struct reader *reader) -{ - char *line_start, *line_end; - - line_start = reader->read_ptr; - line_end = strchr(line_start, '\n'); - /* No newline at EOF */ - if(line_end == NULL){ - line_end = strchr(line_start, '\0'); - } - - if (*line_end == '\n') - line_end++; - - if (*line_end == '\0') - reader->eof = 1; - - reader->line_number++; - reader->read_ptr = line_end; -} - -GIT_INLINE(int) config_keychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name) -{ - int c, rpos; - char *first_quote, *last_quote; - git_buf buf = GIT_BUF_INIT; - size_t quoted_len, alloc_len, base_name_len = strlen(base_name); - - /* - * base_name is what came before the space. We should be at the - * first quotation mark, except for now, line isn't being kept in - * sync so we only really use it to calculate the length. - */ - - first_quote = strchr(line, '"'); - if (first_quote == NULL) { - set_parse_error(reader, 0, "Missing quotation marks in section header"); - goto end_error; - } - - last_quote = strrchr(line, '"'); - quoted_len = last_quote - first_quote; - - if (quoted_len == 0) { - set_parse_error(reader, 0, "Missing closing quotation mark in section header"); - goto end_error; - } - - GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); - GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - if (git_buf_grow(&buf, alloc_len) < 0 || - git_buf_printf(&buf, "%s.", base_name) < 0) - goto end_error; - - rpos = 0; - - line = first_quote; - c = line[++rpos]; - - /* - * At the end of each iteration, whatever is stored in c will be - * added to the string. In case of error, jump to out - */ - do { - - switch (c) { - case 0: - set_parse_error(reader, 0, "Unexpected end-of-line in section header"); - goto end_error; - - case '"': - goto end_parse; - - case '\\': - c = line[++rpos]; - - if (c == 0) { - set_parse_error(reader, rpos, "Unexpected end-of-line in section header"); - goto end_error; - } - - default: - break; - } - - git_buf_putc(&buf, (char)c); - c = line[++rpos]; - } while (line + rpos < last_quote); - -end_parse: - if (git_buf_oom(&buf)) - goto end_error; - - if (line[rpos] != '"' || line[rpos + 1] != ']') { - set_parse_error(reader, rpos, "Unexpected text after closing quotes"); - git_buf_free(&buf); - return -1; - } - - *section_name = git_buf_detach(&buf); - return 0; - -end_error: - git_buf_free(&buf); - - return -1; -} - -static int parse_section_header(struct reader *reader, char **section_out) -{ - char *name, *name_end; - int name_length, c, pos; - int result; - char *line; - size_t line_len; - - line = reader_readline(reader, true); - if (line == NULL) - return -1; - - /* find the end of the variable's name */ - name_end = strrchr(line, ']'); - if (name_end == NULL) { - git__free(line); - set_parse_error(reader, 0, "Missing ']' in section header"); - return -1; - } - - GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); - name = git__malloc(line_len); - GITERR_CHECK_ALLOC(name); - - name_length = 0; - pos = 0; - - /* Make sure we were given a section header */ - c = line[pos++]; - assert(c == '['); - - c = line[pos++]; - - do { - if (git__isspace(c)){ - name[name_length] = '\0'; - result = parse_section_header_ext(reader, line, name, section_out); - git__free(line); - git__free(name); - return result; - } - - if (!config_keychar(c) && c != '.') { - set_parse_error(reader, pos, "Unexpected character in header"); - goto fail_parse; - } - - name[name_length++] = (char)git__tolower(c); - - } while ((c = line[pos++]) != ']'); - - if (line[pos - 1] != ']') { - set_parse_error(reader, pos, "Unexpected end of file"); - goto fail_parse; - } - - git__free(line); - - name[name_length] = 0; - *section_out = name; - - return 0; - -fail_parse: - git__free(line); - git__free(name); - return -1; -} - -static int skip_bom(struct reader *reader) -{ - git_bom_t bom; - int bom_offset = git_buf_text_detect_bom(&bom, - &reader->buffer, reader->read_ptr - reader->buffer.ptr); - - if (bom == GIT_BOM_UTF8) - reader->read_ptr += bom_offset; - - /* TODO: reference implementation is pretty stupid with BoM */ - - return 0; -} - -/* - (* basic types *) - digit = "0".."9" - integer = digit { digit } - alphabet = "a".."z" + "A" .. "Z" - - section_char = alphabet | "." | "-" - extension_char = (* any character except newline *) - any_char = (* any character *) - variable_char = "alphabet" | "-" - - - (* actual grammar *) - config = { section } - - section = header { definition } - - header = "[" section [subsection | subsection_ext] "]" - - subsection = "." section - subsection_ext = "\"" extension "\"" - - section = section_char { section_char } - extension = extension_char { extension_char } - - definition = variable_name ["=" variable_value] "\n" - - variable_name = variable_char { variable_char } - variable_value = string | boolean | integer - - string = quoted_string | plain_string - quoted_string = "\"" plain_string "\"" - plain_string = { any_char } - - boolean = boolean_true | boolean_false - boolean_true = "yes" | "1" | "true" | "on" - boolean_false = "no" | "0" | "false" | "off" -*/ - -static int strip_comments(char *line, int in_quotes) -{ - int quote_count = in_quotes, backslash_count = 0; - char *ptr; - - for (ptr = line; *ptr; ++ptr) { - if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') - quote_count++; - - if ((ptr[0] == ';' || ptr[0] == '#') && - (quote_count % 2) == 0 && - (backslash_count % 2) == 0) { - ptr[0] = '\0'; - break; - } - - if (ptr[0] == '\\') - backslash_count++; - else - backslash_count = 0; - } - - /* skip any space at the end */ - while (ptr > line && git__isspace(ptr[-1])) { - ptr--; - } - ptr[0] = '\0'; - - return quote_count; -} - static int included_path(git_buf *out, const char *dir, const char *path) { /* From the user's home */ @@ -1261,9 +878,6 @@ static int included_path(git_buf *out, const char *dir, const char *path) return git_path_join_unrooted(out, path, dir, NULL); } -static const char *escapes = "ntb\"\\"; -static const char *escaped = "\n\t\b\"\\"; - /* Escape the values to write them to the file */ static char *escape_value(const char *ptr) { @@ -1281,9 +895,9 @@ static char *escape_value(const char *ptr) return NULL; while (*ptr != '\0') { - if ((esc = strchr(escaped, *ptr)) != NULL) { + if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { git_buf_putc(&buf, '\\'); - git_buf_putc(&buf, escapes[esc - escaped]); + git_buf_putc(&buf, git_config_escapes[esc - git_config_escaped]); } else { git_buf_putc(&buf, *ptr); } @@ -1298,264 +912,151 @@ static char *escape_value(const char *ptr) return git_buf_detach(&buf); } -/* '\"' -> '"' etc */ -static int unescape_line( - char **out, bool *is_multi, const char *ptr, int quote_count) -{ - char *str, *fixed, *esc; - size_t ptr_len = strlen(ptr), alloc_len; - - *is_multi = false; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || - (str = git__malloc(alloc_len)) == NULL) { - return -1; - } - - fixed = str; - - while (*ptr != '\0') { - if (*ptr == '"') { - quote_count++; - } else if (*ptr != '\\') { - *fixed++ = *ptr; - } else { - /* backslash, check the next char */ - ptr++; - /* if we're at the end, it's a multiline, so keep the backslash */ - if (*ptr == '\0') { - *is_multi = true; - goto done; - } - if ((esc = strchr(escapes, *ptr)) != NULL) { - *fixed++ = escaped[esc - escapes]; - } else { - git__free(str); - giterr_set(GITERR_CONFIG, "invalid escape at %s", ptr); - return -1; - } - } - ptr++; - } - -done: - *fixed = '\0'; - *out = str; - - return 0; -} - -static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes) -{ - char *line = NULL, *proc_line = NULL; - int quote_count; - bool multiline; - - /* Check that the next line exists */ - line = reader_readline(reader, false); - if (line == NULL) - return -1; - - /* We've reached the end of the file, there is no continuation. - * (this is not an error). - */ - if (line[0] == '\0') { - git__free(line); - return 0; - } - - quote_count = strip_comments(line, !!in_quotes); - - /* If it was just a comment, pretend it didn't exist */ - if (line[0] == '\0') { - git__free(line); - return parse_multiline_variable(reader, value, quote_count); - /* TODO: unbounded recursion. This **could** be exploitable */ - } - - if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) { - git__free(line); - return -1; - } - /* add this line to the multiline var */ - - git_buf_puts(value, proc_line); - git__free(line); - git__free(proc_line); - - /* - * If we need to continue reading the next line, let's just - * keep putting stuff in the buffer - */ - if (multiline) - return parse_multiline_variable(reader, value, quote_count); - - return 0; -} - -GIT_INLINE(bool) is_namechar(char c) -{ - return isalnum(c) || c == '-'; -} - -static int parse_name( - char **name, const char **value, struct reader *reader, const char *line) -{ - const char *name_end = line, *value_start; - - *name = NULL; - *value = NULL; - - while (*name_end && is_namechar(*name_end)) - name_end++; - - if (line == name_end) { - set_parse_error(reader, 0, "Invalid configuration key"); - return -1; - } - - value_start = name_end; - - while (*value_start && git__isspace(*value_start)) - value_start++; - - if (*value_start == '=') { - *value = value_start + 1; - } else if (*value_start) { - set_parse_error(reader, 0, "Invalid configuration key"); - return -1; - } - - if ((*name = git__strndup(line, name_end - line)) == NULL) - return -1; - - return 0; -} - -static int parse_variable(struct reader *reader, char **var_name, char **var_value) -{ - const char *value_start = NULL; - char *line; - int quote_count; - bool multiline; - - line = reader_readline(reader, true); - if (line == NULL) - return -1; - - quote_count = strip_comments(line, 0); - - /* If there is no value, boolean true is assumed */ - *var_value = NULL; - - if (parse_name(var_name, &value_start, reader, line) < 0) - goto on_error; - - /* - * Now, let's try to parse the value - */ - if (value_start != NULL) { - while (git__isspace(value_start[0])) - value_start++; - - if (unescape_line(var_value, &multiline, value_start, 0) < 0) - goto on_error; - - if (multiline) { - git_buf multi_value = GIT_BUF_INIT; - git_buf_attach(&multi_value, *var_value, 0); - - if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || - git_buf_oom(&multi_value)) { - git_buf_free(&multi_value); - goto on_error; - } - - *var_value = git_buf_detach(&multi_value); - } - } - - git__free(line); - return 0; - -on_error: - git__free(*var_name); - git__free(line); - return -1; -} - -static int config_parse( - struct reader *reader, - int (*on_section)(struct reader **reader, const char *current_section, const char *line, size_t line_len, void *data), - int (*on_variable)(struct reader **reader, const char *current_section, char *var_name, char *var_value, const char *line, size_t line_len, void *data), - int (*on_comment)(struct reader **reader, const char *line, size_t line_len, void *data), - int (*on_eof)(struct reader **reader, const char *current_section, void *data), - void *data) -{ - char *current_section = NULL, *var_name, *var_value, *line_start; - char c; - size_t line_len; - int result = 0; - - skip_bom(reader); - - while (result == 0 && !reader->eof) { - line_start = reader->read_ptr; - - c = reader_peek(reader, SKIP_WHITESPACE); - - switch (c) { - case '\0': /* EOF when peeking, set EOF in the reader to exit the loop */ - reader->eof = 1; - break; - - case '[': /* section header, new section begins */ - git__free(current_section); - current_section = NULL; - - if ((result = parse_section_header(reader, ¤t_section)) == 0 && on_section) { - line_len = reader->read_ptr - line_start; - result = on_section(&reader, current_section, line_start, line_len, data); - } - break; - - case '\n': /* comment or whitespace-only */ - case ';': - case '#': - reader_consume_line(reader); - - if (on_comment) { - line_len = reader->read_ptr - line_start; - result = on_comment(&reader, line_start, line_len, data); - } - break; - - default: /* assume variable declaration */ - if ((result = parse_variable(reader, &var_name, &var_value)) == 0 && on_variable) { - line_len = reader->read_ptr - line_start; - result = on_variable(&reader, current_section, var_name, var_value, line_start, line_len, data); - } - break; - } - } - - if (on_eof) - result = on_eof(&reader, current_section, data); - - git__free(current_section); - return result; -} - struct parse_data { + const git_repository *repo; + const char *file_path; git_strmap *values; - diskfile_backend *cfg_file; - uint32_t reader_idx; git_config_level_t level; int depth; }; +static int parse_include(git_config_parser *reader, + struct parse_data *parse_data, const char *file) +{ + struct config_file *include; + git_buf path = GIT_BUF_INIT; + char *dir; + int result; + + if ((result = git_path_dirname_r(&path, reader->file->path)) < 0) + return result; + + dir = git_buf_detach(&path); + result = included_path(&path, dir, file); + git__free(dir); + + if (result < 0) + return result; + + include = git_array_alloc(reader->file->includes); + memset(include, 0, sizeof(*include)); + git_array_init(include->includes); + include->path = git_buf_detach(&path); + + result = config_read(parse_data->values, parse_data->repo, + include, parse_data->level, parse_data->depth+1); + + if (result == GIT_ENOTFOUND) { + giterr_clear(); + result = 0; + } + + return result; +} + +static int do_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value, + bool case_insensitive) +{ + git_buf path = GIT_BUF_INIT; + int error, fnmatch_flags; + + if (value[0] == '.' && git_path_is_dirsep(value[1])) { + git_path_dirname_r(&path, cfg_file); + git_buf_joinpath(&path, path.ptr, value + 2); + } else if (value[0] == '~' && git_path_is_dirsep(value[1])) + git_sysdir_expand_global_file(&path, value + 1); + else if (!git_path_is_absolute(value)) + git_buf_joinpath(&path, "**", value); + else + git_buf_sets(&path, value); + + if (git_buf_oom(&path)) { + error = -1; + goto out; + } + + if (git_path_is_dirsep(value[strlen(value) - 1])) + git_buf_puts(&path, "**"); + + fnmatch_flags = FNM_PATHNAME|FNM_LEADING_DIR; + if (case_insensitive) + fnmatch_flags |= FNM_IGNORECASE; + + if ((error = p_fnmatch(path.ptr, git_repository_path(repo), fnmatch_flags)) < 0) + goto out; + + *matches = (error == 0); + +out: + git_buf_free(&path); + return error; +} + +static int conditional_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + return do_match_gitdir(matches, repo, cfg_file, value, false); +} + +static int conditional_match_gitdir_i( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + return do_match_gitdir(matches, repo, cfg_file, value, true); +} + +static const struct { + const char *prefix; + int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); +} conditions[] = { + { "gitdir:", conditional_match_gitdir }, + { "gitdir/i:", conditional_match_gitdir_i } +}; + +static int parse_conditional_include(git_config_parser *reader, + struct parse_data *parse_data, const char *section, const char *file) +{ + char *condition; + size_t i; + int error = 0, matches; + + if (!parse_data->repo) + return 0; + + condition = git__substrdup(section + strlen("includeIf."), + strlen(section) - strlen("includeIf.") - strlen(".path")); + + for (i = 0; i < ARRAY_SIZE(conditions); i++) { + if (git__prefixcmp(condition, conditions[i].prefix)) + continue; + + if ((error = conditions[i].matches(&matches, + parse_data->repo, + parse_data->file_path, + condition + strlen(conditions[i].prefix))) < 0) + break; + + if (matches) + error = parse_include(reader, parse_data, file); + + break; + } + + git__free(condition); + return error; +} + static int read_on_variable( - struct reader **reader, + git_config_parser *reader, const char *current_section, char *var_name, char *var_value, @@ -1596,74 +1097,61 @@ static int read_on_variable( result = 0; /* Add or append the new config option */ - if (!git__strcmp(var->entry->name, "include.path")) { - struct reader *r; - git_buf path = GIT_BUF_INIT; - char *dir; - uint32_t index; + if (!git__strcmp(var->entry->name, "include.path")) + result = parse_include(reader, parse_data, var->entry->value); + else if (!git__prefixcmp(var->entry->name, "includeif.") && + !git__suffixcmp(var->entry->name, ".path")) + result = parse_conditional_include(reader, parse_data, + var->entry->name, var->entry->value); - r = git_array_alloc(parse_data->cfg_file->readers); - /* The reader may have been reallocated */ - *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx); - memset(r, 0, sizeof(struct reader)); - - if ((result = git_path_dirname_r(&path, (*reader)->file_path)) < 0) - return result; - - /* We need to know our index in the array, as the next config_parse call may realloc */ - index = git_array_size(parse_data->cfg_file->readers) - 1; - dir = git_buf_detach(&path); - result = included_path(&path, dir, var->entry->value); - git__free(dir); - - if (result < 0) - return result; - - r->file_path = git_buf_detach(&path); - git_buf_init(&r->buffer, 0); - - result = git_futils_readbuffer_updated( - &r->buffer, r->file_path, &r->checksum, NULL); - - if (result == 0) { - result = config_read(parse_data->values, parse_data->cfg_file, r, parse_data->level, parse_data->depth+1); - r = git_array_get(parse_data->cfg_file->readers, index); - *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx); - } else if (result == GIT_ENOTFOUND) { - giterr_clear(); - result = 0; - } - - git_buf_free(&r->buffer); - } return result; } -static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth) +static int config_read( + git_strmap *values, + const git_repository *repo, + git_config_file *file, + git_config_level_t level, + int depth) { struct parse_data parse_data; + git_config_parser reader; + git_buf contents = GIT_BUF_INIT; + int error; if (depth >= MAX_INCLUDE_DEPTH) { giterr_set(GITERR_CONFIG, "maximum config include depth reached"); return -1; } + if ((error = git_futils_readbuffer(&contents, file->path)) < 0) + goto out; + + git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); + + if ((error = git_hash_buf(&file->checksum, contents.ptr, contents.size)) < 0) + goto out; + /* Initialize the reading position */ - reader->read_ptr = reader->buffer.ptr; - reader->eof = 0; + reader.file = file; + git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); /* If the file is empty, there's nothing for us to do */ - if (*reader->read_ptr == '\0') - return 0; + if (!reader.ctx.content || *reader.ctx.content == '\0') + goto out; + parse_data.repo = repo; + parse_data.file_path = file->path; parse_data.values = values; - parse_data.cfg_file = cfg_file; - parse_data.reader_idx = git_array_size(cfg_file->readers) - 1; parse_data.level = level; parse_data.depth = depth; - return config_parse(reader, NULL, read_on_variable, NULL, NULL, &parse_data); + error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data); + +out: + git_buf_free(&contents); + return error; } static int write_section(git_buf *fbuf, const char *key) @@ -1719,7 +1207,9 @@ struct write_data { git_buf buffered_comment; unsigned int in_section : 1, preg_replaced : 1; + const char *orig_section; const char *section; + const char *orig_name; const char *name; const regex_t *preg; const char *value; @@ -1747,7 +1237,7 @@ static int write_value(struct write_data *write_data) q = quotes_for_value(write_data->value); result = git_buf_printf(write_data->buf, - "\t%s = %s%s%s\n", write_data->name, q, write_data->value, q); + "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); /* If we are updating a single name/value, we're done. Setting `value` * to `NULL` will prevent us from trying to write it again later (in @@ -1760,7 +1250,7 @@ static int write_value(struct write_data *write_data) } static int write_on_section( - struct reader **reader, + git_config_parser *reader, const char *current_section, const char *line, size_t line_len, @@ -1796,7 +1286,7 @@ static int write_on_section( } static int write_on_variable( - struct reader **reader, + git_config_parser *reader, const char *current_section, char *var_name, char *var_value, @@ -1846,7 +1336,7 @@ static int write_on_variable( return write_value(write_data); } -static int write_on_comment(struct reader **reader, const char *line, size_t line_len, void *data) +static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) { struct write_data *write_data; @@ -1857,7 +1347,7 @@ static int write_on_comment(struct reader **reader, const char *line, size_t lin } static int write_on_eof( - struct reader **reader, const char *current_section, void *data) + git_config_parser *reader, const char *current_section, void *data) { struct write_data *write_data = (struct write_data *)data; int result = 0; @@ -1878,7 +1368,7 @@ static int write_on_eof( if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { /* write the section header unless we're already in it */ if (!current_section || strcmp(current_section, write_data->section)) - result = write_section(write_data->buf, write_data->section); + result = write_section(write_data->buf, write_data->orig_section); if (!result) result = write_value(write_data); @@ -1890,37 +1380,35 @@ static int write_on_eof( /* * This is pretty much the parsing, except we write out anything we don't have */ -static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value) +static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char* value) { int result; - char *section, *name, *ldot; + char *orig_section, *section, *orig_name, *name, *ldot; git_filebuf file = GIT_FILEBUF_INIT; - git_buf buf = GIT_BUF_INIT; - struct reader *reader = git_array_get(cfg->readers, 0); + git_buf buf = GIT_BUF_INIT, contents = GIT_BUF_INIT; + git_config_parser reader; struct write_data write_data; + memset(&reader, 0, sizeof(reader)); + reader.file = &cfg->file; + if (cfg->locked) { - result = git_buf_puts(&reader->buffer, git_buf_cstr(&cfg->locked_content)); + result = git_buf_puts(&contents, git_buf_cstr(&cfg->locked_content)); } else { /* Lock the file */ if ((result = git_filebuf_open( - &file, cfg->file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) { - git_buf_free(&reader->buffer); + &file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) { + git_buf_free(&contents); return result; } /* We need to read in our own config file */ - result = git_futils_readbuffer(&reader->buffer, cfg->file_path); + result = git_futils_readbuffer(&contents, cfg->file.path); } /* Initialise the reading position */ - if (result == GIT_ENOTFOUND) { - reader->read_ptr = NULL; - reader->eof = 1; - git_buf_clear(&reader->buffer); - } else if (result == 0) { - reader->read_ptr = reader->buffer.ptr; - reader->eof = 0; + if (result == 0 || result == GIT_ENOTFOUND) { + git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); } else { git_filebuf_cleanup(&file); return -1; /* OS error when reading the file */ @@ -1929,18 +1417,32 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ldot = strrchr(key, '.'); name = ldot + 1; section = git__strndup(key, ldot - key); + GITERR_CHECK_ALLOC(section); + + ldot = strrchr(orig_key, '.'); + orig_name = ldot + 1; + orig_section = git__strndup(orig_key, ldot - orig_key); + GITERR_CHECK_ALLOC(orig_section); write_data.buf = &buf; git_buf_init(&write_data.buffered_comment, 0); + write_data.orig_section = orig_section; write_data.section = section; write_data.in_section = 0; write_data.preg_replaced = 0; + write_data.orig_name = orig_name; write_data.name = name; write_data.preg = preg; write_data.value = value; - result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data); + result = git_config_parse(&reader, + write_on_section, + write_on_variable, + write_on_comment, + write_on_eof, + &write_data); git__free(section); + git__free(orig_section); git_buf_free(&write_data.buffered_comment); if (result < 0) { @@ -1960,6 +1462,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p done: git_buf_free(&buf); - git_buf_free(&reader->buffer); + git_buf_free(&contents); + git_parse_ctx_clear(&reader.ctx); return result; } diff --git a/src/config_file.h b/src/config_file.h index 654e6cacf..72818e58c 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -7,12 +7,14 @@ #ifndef INCLUDE_config_file_h__ #define INCLUDE_config_file_h__ +#include "common.h" + #include "git2/sys/config.h" #include "git2/config.h" -GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level) +GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) { - return cfg->open(cfg, level); + return cfg->open(cfg, level, repo); } GIT_INLINE(void) git_config_file_free(git_config_backend *cfg) @@ -69,4 +71,3 @@ GIT_INLINE(int) git_config_file_unlock(git_config_backend *cfg, int success) extern int git_config_file_normalize_section(char *start, char *end); #endif - diff --git a/src/config_parse.c b/src/config_parse.c new file mode 100644 index 000000000..149550dcd --- /dev/null +++ b/src/config_parse.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "config_parse.h" + +#include "buf_text.h" + +#include + +static void set_parse_error(git_config_parser *reader, int col, const char *error_str) +{ + giterr_set(GITERR_CONFIG, "failed to parse config file: %s (in %s:%"PRIuZ", column %d)", + error_str, reader->file->path, reader->ctx.line_num, col); +} + + +GIT_INLINE(int) config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int strip_comments(char *line, int in_quotes) +{ + int quote_count = in_quotes, backslash_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && + (quote_count % 2) == 0 && + (backslash_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + + if (ptr[0] == '\\') + backslash_count++; + else + backslash_count = 0; + } + + /* skip any space at the end */ + while (ptr > line && git__isspace(ptr[-1])) { + ptr--; + } + ptr[0] = '\0'; + + return quote_count; +} + + +static int parse_section_header_ext(git_config_parser *reader, const char *line, const char *base_name, char **section_name) +{ + int c, rpos; + char *first_quote, *last_quote; + git_buf buf = GIT_BUF_INIT; + size_t quoted_len, alloc_len, base_name_len = strlen(base_name); + + /* + * base_name is what came before the space. We should be at the + * first quotation mark, except for now, line isn't being kept in + * sync so we only really use it to calculate the length. + */ + + first_quote = strchr(line, '"'); + if (first_quote == NULL) { + set_parse_error(reader, 0, "Missing quotation marks in section header"); + goto end_error; + } + + last_quote = strrchr(line, '"'); + quoted_len = last_quote - first_quote; + + if (quoted_len == 0) { + set_parse_error(reader, 0, "Missing closing quotation mark in section header"); + goto end_error; + } + + GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); + GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + if (git_buf_grow(&buf, alloc_len) < 0 || + git_buf_printf(&buf, "%s.", base_name) < 0) + goto end_error; + + rpos = 0; + + line = first_quote; + c = line[++rpos]; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + + switch (c) { + case 0: + set_parse_error(reader, 0, "Unexpected end-of-line in section header"); + goto end_error; + + case '"': + goto end_parse; + + case '\\': + c = line[++rpos]; + + if (c == 0) { + set_parse_error(reader, rpos, "Unexpected end-of-line in section header"); + goto end_error; + } + + default: + break; + } + + git_buf_putc(&buf, (char)c); + c = line[++rpos]; + } while (line + rpos < last_quote); + +end_parse: + if (git_buf_oom(&buf)) + goto end_error; + + if (line[rpos] != '"' || line[rpos + 1] != ']') { + set_parse_error(reader, rpos, "Unexpected text after closing quotes"); + git_buf_free(&buf); + return -1; + } + + *section_name = git_buf_detach(&buf); + return 0; + +end_error: + git_buf_free(&buf); + + return -1; +} + +static int parse_section_header(git_config_parser *reader, char **section_out) +{ + char *name, *name_end; + int name_length, c, pos; + int result; + char *line; + size_t line_len; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + if (line == NULL) + return -1; + + /* find the end of the variable's name */ + name_end = strrchr(line, ']'); + if (name_end == NULL) { + git__free(line); + set_parse_error(reader, 0, "Missing ']' in section header"); + return -1; + } + + GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); + name = git__malloc(line_len); + GITERR_CHECK_ALLOC(name); + + name_length = 0; + pos = 0; + + /* Make sure we were given a section header */ + c = line[pos++]; + assert(c == '['); + + c = line[pos++]; + + do { + if (git__isspace(c)){ + name[name_length] = '\0'; + result = parse_section_header_ext(reader, line, name, section_out); + git__free(line); + git__free(name); + return result; + } + + if (!config_keychar(c) && c != '.') { + set_parse_error(reader, pos, "Unexpected character in header"); + goto fail_parse; + } + + name[name_length++] = (char)git__tolower(c); + + } while ((c = line[pos++]) != ']'); + + if (line[pos - 1] != ']') { + set_parse_error(reader, pos, "Unexpected end of file"); + goto fail_parse; + } + + git__free(line); + + name[name_length] = 0; + *section_out = name; + + return 0; + +fail_parse: + git__free(line); + git__free(name); + return -1; +} + +static int skip_bom(git_parse_ctx *parser) +{ + git_buf buf = GIT_BUF_INIT_CONST(parser->content, parser->content_len); + git_bom_t bom; + int bom_offset = git_buf_text_detect_bom(&bom, &buf); + + if (bom == GIT_BOM_UTF8) + git_parse_advance_chars(parser, bom_offset); + + /* TODO: reference implementation is pretty stupid with BoM */ + + return 0; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +/* '\"' -> '"' etc */ +static int unescape_line( + char **out, bool *is_multi, const char *ptr, int quote_count) +{ + char *str, *fixed, *esc; + size_t ptr_len = strlen(ptr), alloc_len; + + *is_multi = false; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || + (str = git__malloc(alloc_len)) == NULL) { + return -1; + } + + fixed = str; + + while (*ptr != '\0') { + if (*ptr == '"') { + quote_count++; + } else if (*ptr != '\\') { + *fixed++ = *ptr; + } else { + /* backslash, check the next char */ + ptr++; + /* if we're at the end, it's a multiline, so keep the backslash */ + if (*ptr == '\0') { + *is_multi = true; + goto done; + } + if ((esc = strchr(git_config_escapes, *ptr)) != NULL) { + *fixed++ = git_config_escaped[esc - git_config_escapes]; + } else { + git__free(str); + giterr_set(GITERR_CONFIG, "invalid escape at %s", ptr); + return -1; + } + } + ptr++; + } + +done: + *fixed = '\0'; + *out = str; + + return 0; +} + +static int parse_multiline_variable(git_config_parser *reader, git_buf *value, int in_quotes) +{ + char *line = NULL, *proc_line = NULL; + int quote_count; + bool multiline; + + /* Check that the next line exists */ + git_parse_advance_line(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + if (line == NULL) + return -1; + + /* We've reached the end of the file, there is no continuation. + * (this is not an error). + */ + if (line[0] == '\0') { + git__free(line); + return 0; + } + + quote_count = strip_comments(line, !!in_quotes); + + /* If it was just a comment, pretend it didn't exist */ + if (line[0] == '\0') { + git__free(line); + return parse_multiline_variable(reader, value, quote_count); + /* TODO: unbounded recursion. This **could** be exploitable */ + } + + if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) { + git__free(line); + return -1; + } + /* add this line to the multiline var */ + + git_buf_puts(value, proc_line); + git__free(line); + git__free(proc_line); + + /* + * If we need to continue reading the next line, let's just + * keep putting stuff in the buffer + */ + if (multiline) + return parse_multiline_variable(reader, value, quote_count); + + return 0; +} + +GIT_INLINE(bool) is_namechar(char c) +{ + return isalnum(c) || c == '-'; +} + +static int parse_name( + char **name, const char **value, git_config_parser *reader, const char *line) +{ + const char *name_end = line, *value_start; + + *name = NULL; + *value = NULL; + + while (*name_end && is_namechar(*name_end)) + name_end++; + + if (line == name_end) { + set_parse_error(reader, 0, "Invalid configuration key"); + return -1; + } + + value_start = name_end; + + while (*value_start && git__isspace(*value_start)) + value_start++; + + if (*value_start == '=') { + *value = value_start + 1; + } else if (*value_start) { + set_parse_error(reader, 0, "Invalid configuration key"); + return -1; + } + + if ((*name = git__strndup(line, name_end - line)) == NULL) + return -1; + + return 0; +} + +static int parse_variable(git_config_parser *reader, char **var_name, char **var_value) +{ + const char *value_start = NULL; + char *line; + int quote_count; + bool multiline; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + if (line == NULL) + return -1; + + quote_count = strip_comments(line, 0); + + /* If there is no value, boolean true is assumed */ + *var_value = NULL; + + if (parse_name(var_name, &value_start, reader, line) < 0) + goto on_error; + + /* + * Now, let's try to parse the value + */ + if (value_start != NULL) { + while (git__isspace(value_start[0])) + value_start++; + + if (unescape_line(var_value, &multiline, value_start, 0) < 0) + goto on_error; + + if (multiline) { + git_buf multi_value = GIT_BUF_INIT; + git_buf_attach(&multi_value, *var_value, 0); + + if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || + git_buf_oom(&multi_value)) { + git_buf_free(&multi_value); + goto on_error; + } + + *var_value = git_buf_detach(&multi_value); + } + } + + git__free(line); + return 0; + +on_error: + git__free(*var_name); + git__free(line); + return -1; +} + +int git_config_parse( + git_config_parser *parser, + git_config_parser_section_cb on_section, + git_config_parser_variable_cb on_variable, + git_config_parser_comment_cb on_comment, + git_config_parser_eof_cb on_eof, + void *data) +{ + git_parse_ctx *ctx; + char *current_section = NULL, *var_name, *var_value; + int result = 0; + + ctx = &parser->ctx; + + skip_bom(ctx); + + for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) { + const char *line_start = parser->ctx.line; + size_t line_len = parser->ctx.line_len; + char c; + + /* + * Get either first non-whitespace character or, if that does + * not exist, the first whitespace character. This is required + * to preserve whitespaces when writing back the file. + */ + if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 && + git_parse_peek(&c, ctx, 0) < 0) + continue; + + switch (c) { + case '[': /* section header, new section begins */ + git__free(current_section); + current_section = NULL; + + if ((result = parse_section_header(parser, ¤t_section)) == 0 && on_section) { + result = on_section(parser, current_section, line_start, line_len, data); + } + break; + + case '\n': /* comment or whitespace-only */ + case '\r': + case ' ': + case '\t': + case ';': + case '#': + if (on_comment) { + result = on_comment(parser, line_start, line_len, data); + } + break; + + default: /* assume variable declaration */ + if ((result = parse_variable(parser, &var_name, &var_value)) == 0 && on_variable) { + result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, data); + } + break; + } + + if (result < 0) + goto out; + } + + if (on_eof) + result = on_eof(parser, current_section, data); + +out: + git__free(current_section); + return result; +} diff --git a/src/config_parse.h b/src/config_parse.h new file mode 100644 index 000000000..d14a8e60c --- /dev/null +++ b/src/config_parse.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_parse_h__ +#define INCLUDE_config_parse_h__ + +#include "common.h" +#include "array.h" +#include "oid.h" +#include "parse.h" + +static const char *git_config_escapes = "ntb\"\\"; +static const char *git_config_escaped = "\n\t\b\"\\"; + +typedef struct config_file { + git_oid checksum; + char *path; + git_array_t(struct config_file) includes; +} git_config_file; + +typedef struct { + struct config_file *file; + git_parse_ctx ctx; +} git_config_parser; + +typedef int (*git_config_parser_section_cb)( + git_config_parser *parser, + const char *current_section, + const char *line, + size_t line_len, + void *data); + +typedef int (*git_config_parser_variable_cb)( + git_config_parser *parser, + const char *current_section, + char *var_name, + char *var_value, + const char *line, + size_t line_len, + void *data); + +typedef int (*git_config_parser_comment_cb)( + git_config_parser *parser, + const char *line, + size_t line_len, + void *data); + +typedef int (*git_config_parser_eof_cb)( + git_config_parser *parser, + const char *current_section, + void *data); + +int git_config_parse( + git_config_parser *parser, + git_config_parser_section_cb on_section, + git_config_parser_variable_cb on_variable, + git_config_parser_comment_cb on_comment, + git_config_parser_eof_cb on_eof, + void *data); + +#endif diff --git a/src/crlf.c b/src/crlf.c index b8ae5cda1..9af60076d 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -5,12 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include "git2/attr.h" #include "git2/blob.h" #include "git2/index.h" #include "git2/sys/filter.h" -#include "common.h" #include "fileops.h" #include "hash.h" #include "filter.h" diff --git a/src/delta.h b/src/delta.h index cc9372922..f61987304 100644 --- a/src/delta.h +++ b/src/delta.h @@ -6,6 +6,7 @@ #define INCLUDE_git_delta_h__ #include "common.h" + #include "pack.h" typedef struct git_delta_index git_delta_index; diff --git a/src/describe.c b/src/describe.c index 4a1e25378..edf8edfd1 100644 --- a/src/describe.c +++ b/src/describe.c @@ -4,12 +4,14 @@ * 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 "common.h" + #include "git2/describe.h" #include "git2/strarray.h" #include "git2/diff.h" #include "git2/status.h" -#include "common.h" #include "commit.h" #include "commit_list.h" #include "oidmap.h" diff --git a/src/diff.c b/src/diff.c index a93bd4cd0..c7a652896 100644 --- a/src/diff.c +++ b/src/diff.c @@ -4,20 +4,20 @@ * 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 "git2/version.h" -#include "common.h" + #include "diff.h" + +#include "git2/version.h" #include "diff_generate.h" #include "patch.h" #include "commit.h" #include "index.h" -#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ - (((DIFF)->opts.flags & (FLAG)) != 0) -#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ - (((DIFF)->opts.flags & (FLAG)) == 0) -#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \ - (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL)) +struct patch_id_args { + git_hash_ctx ctx; + git_oid result; + int first_file; +}; GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) { @@ -374,3 +374,142 @@ int git_diff_format_email_init_options( return 0; } +static int flush_hunk(git_oid *result, git_hash_ctx *ctx) +{ + git_oid hash; + unsigned short carry = 0; + int error, i; + + if ((error = git_hash_final(&hash, ctx)) < 0 || + (error = git_hash_init(ctx)) < 0) + return error; + + for (i = 0; i < GIT_OID_RAWSZ; i++) { + carry += result->id[i] + hash.id[i]; + result->id[i] = carry; + carry >>= 8; + } + + return 0; +} + +static void strip_spaces(git_buf *buf) +{ + char *src = buf->ptr, *dst = buf->ptr; + char c; + size_t len = 0; + + while ((c = *src++) != '\0') { + if (!git__isspace(c)) { + *dst++ = c; + len++; + } + } + + git_buf_truncate(buf, len); +} + +static int file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + struct patch_id_args *args = (struct patch_id_args *) payload; + git_buf buf = GIT_BUF_INIT; + int error; + + GIT_UNUSED(progress); + + if (!args->first_file && + (error = flush_hunk(&args->result, &args->ctx)) < 0) + goto out; + args->first_file = 0; + + if ((error = git_buf_printf(&buf, + "diff--gita/%sb/%s---a/%s+++b/%s", + delta->old_file.path, + delta->new_file.path, + delta->old_file.path, + delta->new_file.path)) < 0) + goto out; + + strip_spaces(&buf); + + if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) + goto out; + +out: + git_buf_free(&buf); + return error; +} + +static int line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + struct patch_id_args *args = (struct patch_id_args *) payload; + git_buf buf = GIT_BUF_INIT; + int error; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: + git_buf_putc(&buf, '+'); + break; + case GIT_DIFF_LINE_DELETION: + git_buf_putc(&buf, '-'); + break; + case GIT_DIFF_LINE_CONTEXT: + break; + default: + giterr_set(GITERR_PATCH, "invalid line origin for patch"); + return -1; + } + + git_buf_put(&buf, line->content, line->content_len); + strip_spaces(&buf); + + if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) + goto out; + +out: + git_buf_free(&buf); + return error; +} + +int git_diff_patchid_init_options(git_diff_patchid_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT); + return 0; +} + +int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts) +{ + struct patch_id_args args; + int error; + + GITERR_CHECK_VERSION( + opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options"); + + memset(&args, 0, sizeof(args)); + args.first_file = 1; + if ((error = git_hash_ctx_init(&args.ctx)) < 0) + goto out; + + if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, line_cb, &args)) < 0) + goto out; + + if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) + goto out; + + git_oid_cpy(out, &args.result); + +out: + git_hash_ctx_cleanup(&args.ctx); + return error; +} diff --git a/src/diff.h b/src/diff.h index 5750d2a09..93374b96e 100644 --- a/src/diff.h +++ b/src/diff.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_diff_h__ #define INCLUDE_diff_h__ +#include "common.h" + #include "git2/diff.h" #include "git2/patch.h" #include "git2/sys/diff.h" @@ -32,6 +34,7 @@ typedef enum { struct git_diff { git_refcount rc; git_repository *repo; + git_attr_session attrsession; git_diff_origin_t type; git_diff_options opts; git_vector deltas; /* vector of git_diff_delta */ @@ -63,4 +66,3 @@ extern int git_diff__entry_cmp(const void *a, const void *b); extern int git_diff__entry_icmp(const void *a, const void *b); #endif - diff --git a/src/diff_driver.c b/src/diff_driver.c index 9109f3155..7114b06b6 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -4,12 +4,12 @@ * 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 "common.h" + +#include "diff_driver.h" #include "git2/attr.h" #include "diff.h" -#include "diff_driver.h" #include "strmap.h" #include "map.h" #include "buf_text.h" @@ -354,27 +354,30 @@ done: } int git_diff_driver_lookup( - git_diff_driver **out, git_repository *repo, const char *path) + git_diff_driver **out, git_repository *repo, + git_attr_session *attrsession, const char *path) { int error = 0; - const char *value; + const char *values[1], *attrs[] = { "diff" }; assert(out); *out = NULL; if (!repo || !path || !strlen(path)) /* just use the auto value */; - else if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0) + else if ((error = git_attr_get_many_with_session(values, repo, + attrsession, 0, path, 1, attrs)) < 0) /* return error below */; - else if (GIT_ATTR_UNSPECIFIED(value)) + + else if (GIT_ATTR_UNSPECIFIED(values[0])) /* just use the auto value */; - else if (GIT_ATTR_FALSE(value)) + else if (GIT_ATTR_FALSE(values[0])) *out = &global_drivers[DIFF_DRIVER_BINARY]; - else if (GIT_ATTR_TRUE(value)) + else if (GIT_ATTR_TRUE(values[0])) *out = &global_drivers[DIFF_DRIVER_TEXT]; /* otherwise look for driver information in config and build driver */ - else if ((error = git_diff_driver_load(out, repo, value)) < 0) { + else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) { if (error == GIT_ENOTFOUND) { error = 0; giterr_clear(); diff --git a/src/diff_driver.h b/src/diff_driver.h index 0706dcfc5..a03a67e67 100644 --- a/src/diff_driver.h +++ b/src/diff_driver.h @@ -8,6 +8,8 @@ #define INCLUDE_diff_driver_h__ #include "common.h" + +#include "attr_file.h" #include "buffer.h" typedef struct git_diff_driver_registry git_diff_driver_registry; @@ -17,7 +19,8 @@ void git_diff_driver_registry_free(git_diff_driver_registry *); typedef struct git_diff_driver git_diff_driver; -int git_diff_driver_lookup(git_diff_driver **, git_repository *, const char *); +int git_diff_driver_lookup(git_diff_driver **, git_repository *, + git_attr_session *attrsession, const char *); void git_diff_driver_free(git_diff_driver *); /* diff option flags to force off and on for this driver */ diff --git a/src/diff_file.c b/src/diff_file.c index d5fc5e940..5bb9c372a 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -4,12 +4,13 @@ * 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 "common.h" + +#include "diff_file.h" + #include "git2/blob.h" #include "git2/submodule.h" #include "diff.h" #include "diff_generate.h" -#include "diff_file.h" #include "odb.h" #include "fileops.h" #include "filter.h" @@ -53,7 +54,8 @@ static int diff_file_content_init_common( fc->src = GIT_ITERATOR_TYPE_TREE; if (!fc->driver && - git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) + git_diff_driver_lookup(&fc->driver, fc->repo, + NULL, fc->file->path) < 0) return -1; /* give driver a chance to modify options */ @@ -100,7 +102,8 @@ int git_diff_file_content__init_from_diff( fc->file = use_old ? &delta->old_file : &delta->new_file; fc->src = use_old ? diff->old_src : diff->new_src; - if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) + if (git_diff_driver_lookup(&fc->driver, fc->repo, + &diff->attrsession, fc->file->path) < 0) return -1; switch (delta->status) { @@ -138,7 +141,6 @@ int git_diff_file_content__init_from_src( memset(fc, 0, sizeof(*fc)); fc->repo = repo; fc->file = as_file; - fc->blob = src->blob; if (!src->blob && !src->buf) { fc->flags |= GIT_DIFF_FLAG__NO_DATA; @@ -148,12 +150,15 @@ int git_diff_file_content__init_from_src( fc->file->mode = GIT_FILEMODE_BLOB; if (src->blob) { + git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob); fc->file->size = git_blob_rawsize(src->blob); git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); fc->file->id_abbrev = GIT_OID_HEXSZ; fc->map.len = (size_t)fc->file->size; fc->map.data = (char *)git_blob_rawcontent(src->blob); + + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; } else { fc->file->size = src->buflen; git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB); diff --git a/src/diff_file.h b/src/diff_file.h index 0d54b6d33..5da7a7bb8 100644 --- a/src/diff_file.h +++ b/src/diff_file.h @@ -8,6 +8,7 @@ #define INCLUDE_diff_file_h__ #include "common.h" + #include "diff.h" #include "diff_driver.h" #include "map.h" diff --git a/src/diff_generate.c b/src/diff_generate.c index f6cc04fed..e11cbe4e4 100644 --- a/src/diff_generate.c +++ b/src/diff_generate.c @@ -4,9 +4,10 @@ * 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 "common.h" -#include "diff.h" + #include "diff_generate.h" + +#include "diff.h" #include "patch_generate.h" #include "fileops.h" #include "config.h" @@ -23,7 +24,7 @@ (((DIFF)->base.opts.flags & (FLAG)) == 0) #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ - ((DIFF)->base.opts.flags & ~(VAL)) + ((DIFF)->base.opts.flags & ~(FLAG)) typedef struct { struct git_diff base; @@ -388,6 +389,7 @@ static void diff_generated_free(git_diff *d) { git_diff_generated *diff = (git_diff_generated *)d; + git_attr_session__free(&diff->base.attrsession); git_vector_free_deep(&diff->base.deltas); git_pathspec__vfree(&diff->pathspec); @@ -410,13 +412,14 @@ static git_diff_generated *diff_generated_alloc( if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) return NULL; - GIT_REFCOUNT_INC(diff); + GIT_REFCOUNT_INC(&diff->base); diff->base.type = GIT_DIFF_TYPE_GENERATED; diff->base.repo = repo; diff->base.old_src = old_iter->type; diff->base.new_src = new_iter->type; diff->base.patch_fn = git_patch_generated_from_diff; diff->base.free_fn = diff_generated_free; + git_attr_session__init(&diff->base.attrsession, repo); memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); git_pool_init(&diff->base.pool, 1); diff --git a/src/diff_generate.h b/src/diff_generate.h index 63e7f0532..3f182b0ba 100644 --- a/src/diff_generate.h +++ b/src/diff_generate.h @@ -7,6 +7,12 @@ #ifndef INCLUDE_diff_generate_h__ #define INCLUDE_diff_generate_h__ +#include "common.h" + +#include "diff.h" +#include "pool.h" +#include "index.h" + enum { GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ @@ -120,4 +126,3 @@ GIT_INLINE(int) git_diff_file__resolve_zero_size( } #endif - diff --git a/src/diff_parse.c b/src/diff_parse.c index 5e3a7a177..59fd8612a 100644 --- a/src/diff_parse.c +++ b/src/diff_parse.c @@ -4,9 +4,10 @@ * 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 "common.h" -#include "diff.h" + #include "diff_parse.h" + +#include "diff.h" #include "patch.h" #include "patch_parse.h" @@ -35,7 +36,7 @@ static git_diff_parsed *diff_parsed_alloc(void) if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) return NULL; - GIT_REFCOUNT_INC(diff); + GIT_REFCOUNT_INC(&diff->base); diff->base.type = GIT_DIFF_TYPE_PARSED; diff->base.strcomp = git__strcmp; diff->base.strncomp = git__strncmp; @@ -82,7 +83,7 @@ int git_diff_from_buffer( ctx = git_patch_parse_ctx_init(content, content_len, NULL); GITERR_CHECK_ALLOC(ctx); - while (ctx->remain_len) { + while (ctx->parse_ctx.remain_len) { if ((error = git_patch_parse(&patch, ctx)) < 0) break; diff --git a/src/diff_parse.h b/src/diff_parse.h index c47d4cbc9..876782128 100644 --- a/src/diff_parse.h +++ b/src/diff_parse.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_diff_parse_h__ #define INCLUDE_diff_parse_h__ +#include "common.h" + #include "diff.h" typedef struct { diff --git a/src/diff_print.c b/src/diff_print.c index 5aa8a37e6..28ae38424 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -4,7 +4,9 @@ * 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 "common.h" + #include "diff.h" #include "diff_file.h" #include "patch_generate.h" diff --git a/src/diff_stats.c b/src/diff_stats.c index 2005712cd..583c68459 100644 --- a/src/diff_stats.c +++ b/src/diff_stats.c @@ -4,7 +4,9 @@ * 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 "common.h" + #include "vector.h" #include "diff.h" #include "patch_generate.h" diff --git a/src/diff_tform.c b/src/diff_tform.c index b004ddd66..bc664dd05 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -4,7 +4,8 @@ * 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 "common.h" + +#include "diff_tform.h" #include "git2/config.h" #include "git2/blob.h" @@ -685,8 +686,10 @@ static bool is_rename_target( break; } if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && - delta->similarity < opts->rename_from_rewrite_threshold) + delta->similarity < opts->rename_from_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; break; + } return false; diff --git a/src/diff_tform.h b/src/diff_tform.h index 5bd9712d9..7abb8b3fe 100644 --- a/src/diff_tform.h +++ b/src/diff_tform.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_diff_tform_h__ #define INCLUDE_diff_tform_h__ +#include "common.h" + +#include "diff_file.h" + extern int git_diff_find_similar__hashsig_for_file( void **out, const git_diff_file *f, const char *path, void *p); @@ -19,4 +23,3 @@ extern int git_diff_find_similar__calc_similarity( int *score, void *siga, void *sigb, void *payload); #endif - diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c index 60c4d85cb..701eb1b5f 100644 --- a/src/diff_xdiff.c +++ b/src/diff_xdiff.c @@ -4,11 +4,12 @@ * 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 "diff_xdiff.h" + #include "git2/errors.h" -#include "common.h" #include "diff.h" #include "diff_driver.h" -#include "diff_xdiff.h" #include "patch_generate.h" static int git_xdiff_scan_int(const char **str, int *value) @@ -238,6 +239,8 @@ void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (flags & GIT_DIFF_INDENT_HEURISTIC) + xo->params.flags |= XDF_INDENT_HEURISTIC; if (flags & GIT_DIFF_PATIENCE) xo->params.flags |= XDF_PATIENCE_DIFF; diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h index 88375986b..aca80b131 100644 --- a/src/diff_xdiff.h +++ b/src/diff_xdiff.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_diff_xdiff_h__ #define INCLUDE_diff_xdiff_h__ +#include "common.h" + #include "diff.h" #include "xdiff/xdiff.h" #include "patch_generate.h" diff --git a/src/errors.c b/src/errors.c index 91acc3541..a874163b0 100644 --- a/src/errors.c +++ b/src/errors.c @@ -4,7 +4,9 @@ * 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 "common.h" + #include "global.h" #include "posix.h" #include "buffer.h" diff --git a/src/features.h.in b/src/features.h.in new file mode 100644 index 000000000..e03b7a251 --- /dev/null +++ b/src/features.h.in @@ -0,0 +1,36 @@ +#ifndef INCLUDE_features_h__ +#define INCLUDE_features_h__ + +#cmakedefine GIT_DEBUG_POOL 1 +#cmakedefine GIT_TRACE 1 +#cmakedefine GIT_THREADS 1 +#cmakedefine GIT_MSVC_CRTDBG 1 + +#cmakedefine GIT_ARCH_64 1 +#cmakedefine GIT_ARCH_32 1 + +#cmakedefine GIT_USE_ICONV 1 +#cmakedefine GIT_USE_NSEC 1 +#cmakedefine GIT_USE_STAT_MTIM 1 +#cmakedefine GIT_USE_STAT_MTIMESPEC 1 +#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 +#cmakedefine GIT_USE_FUTIMENS 1 +#cmakedefine GIT_USE_REGCOMP_L 1 + +#cmakedefine GIT_SSH 1 +#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 + +#cmakedefine GIT_GSSAPI 1 +#cmakedefine GIT_WINHTTP 1 +#cmakedefine GIT_CURL 1 + +#cmakedefine GIT_HTTPS 1 +#cmakedefine GIT_OPENSSL 1 +#cmakedefine GIT_SECURE_TRANSPORT 1 + +#cmakedefine GIT_SHA1_COLLISIONDETECT 1 +#cmakedefine GIT_SHA1_WIN32 1 +#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 +#cmakedefine GIT_SHA1_OPENSSL 1 + +#endif diff --git a/src/fetch.c b/src/fetch.c index f408a5174..0b22b3673 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -5,16 +5,16 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "fetch.h" + #include "git2/oid.h" #include "git2/refs.h" #include "git2/revwalk.h" #include "git2/transport.h" -#include "common.h" #include "remote.h" #include "refspec.h" #include "pack.h" -#include "fetch.h" #include "netops.h" #include "repository.h" #include "refs.h" diff --git a/src/fetch.h b/src/fetch.h index 0412d4e44..1c75af9c3 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_fetch_h__ #define INCLUDE_fetch_h__ +#include "common.h" + +#include "git2/remote.h" + #include "netops.h" int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); diff --git a/src/fetchhead.c b/src/fetchhead.c index 6e6f3eb5e..e55e7c85b 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -5,11 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "fetchhead.h" + #include "git2/types.h" #include "git2/oid.h" -#include "fetchhead.h" -#include "common.h" #include "buffer.h" #include "fileops.h" #include "filebuf.h" @@ -118,7 +118,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) return -1; - if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { + if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) { git_buf_free(&path); return -1; } diff --git a/src/fetchhead.h b/src/fetchhead.h index b03bd0f74..9e5171010 100644 --- a/src/fetchhead.h +++ b/src/fetchhead.h @@ -7,6 +7,9 @@ #ifndef INCLUDE_fetchhead_h__ #define INCLUDE_fetchhead_h__ +#include "common.h" + +#include "oid.h" #include "vector.h" typedef struct git_fetchhead_ref { diff --git a/src/filebuf.c b/src/filebuf.c index 80250ccdf..8b7e489da 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -4,8 +4,9 @@ * 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 "common.h" + #include "filebuf.h" + #include "fileops.h" static const size_t WRITE_BUFFER_SIZE = (4096 * 2); diff --git a/src/filebuf.h b/src/filebuf.h index c65aea780..f51ff230f 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_filebuf_h__ #define INCLUDE_filebuf_h__ +#include "common.h" + #include "fileops.h" #include "hash.h" #include diff --git a/src/fileops.c b/src/fileops.c index 2f3f58d4f..58988c2d2 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -4,8 +4,9 @@ * 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 "common.h" + #include "fileops.h" + #include "global.h" #include "strmap.h" #include @@ -101,6 +102,16 @@ int git_futils_open_ro(const char *path) return fd; } +int git_futils_truncate(const char *path, int mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + if (fd < 0) + return git_path_set_error(errno, path, "open"); + + close(fd); + return 0; +} + git_off_t git_futils_filesize(git_file fd) { struct stat sb; diff --git a/src/fileops.h b/src/fileops.h index 46886b0d7..2844ece21 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -8,6 +8,7 @@ #define INCLUDE_fileops_h__ #include "common.h" + #include "map.h" #include "posix.h" #include "path.h" @@ -246,6 +247,11 @@ extern int git_futils_cp_r( */ extern int git_futils_open_ro(const char *path); +/** + * Truncate a file, creating it if it doesn't exist. + */ +extern int git_futils_truncate(const char *path, int mode); + /** * Get the filesize in bytes of a file */ @@ -381,4 +387,4 @@ extern int git_futils_fsync_dir(const char *path); */ extern int git_futils_fsync_parent(const char *path); -#endif /* INCLUDE_fileops_h__ */ +#endif diff --git a/src/filter.c b/src/filter.c index 361e08529..6ab09790b 100644 --- a/src/filter.c +++ b/src/filter.c @@ -5,10 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "filter.h" + #include "common.h" #include "fileops.h" #include "hash.h" -#include "filter.h" #include "repository.h" #include "global.h" #include "git2/sys/filter.h" diff --git a/src/filter.h b/src/filter.h index 9bd835f94..b1c403ba9 100644 --- a/src/filter.h +++ b/src/filter.h @@ -8,6 +8,7 @@ #define INCLUDE_filter_h__ #include "common.h" + #include "attr_file.h" #include "git2/filter.h" diff --git a/src/fnmatch.c b/src/fnmatch.c index 33c8a2512..3cc2a27ba 100644 --- a/src/fnmatch.c +++ b/src/fnmatch.c @@ -44,12 +44,12 @@ * Compares a filename or pathname to a pattern. */ +#include "fnmatch.h" + #include #include #include -#include "fnmatch.h" - #define EOS '\0' #define RANGE_MATCH 1 diff --git a/src/fnmatch.h b/src/fnmatch.h index 88af45939..ddaae15bb 100644 --- a/src/fnmatch.h +++ b/src/fnmatch.h @@ -25,8 +25,8 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ -#ifndef INCLUDE_fnmatch__compat_h__ -#define INCLUDE_fnmatch__compat_h__ +#ifndef INCLUDE_fnmatch_h__ +#define INCLUDE_fnmatch_h__ #include "common.h" @@ -45,5 +45,4 @@ extern int p_fnmatch(const char *pattern, const char *string, int flags); -#endif /* _FNMATCH_H */ - +#endif diff --git a/src/global.c b/src/global.c index afa57e1d6..2f9b45bcd 100644 --- a/src/global.c +++ b/src/global.c @@ -4,13 +4,15 @@ * 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 "common.h" + #include "global.h" + #include "hash.h" #include "sysdir.h" #include "filter.h" #include "merge_driver.h" -#include "openssl_stream.h" +#include "streams/curl.h" +#include "streams/openssl.h" #include "thread-utils.h" #include "git2/global.h" #include "transports/ssh.h" @@ -22,7 +24,7 @@ git_mutex git__mwindow_mutex; -#define MAX_SHUTDOWN_CB 9 +#define MAX_SHUTDOWN_CB 10 static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; static git_atomic git__n_shutdown_callbacks; @@ -62,7 +64,8 @@ static int init_common(void) (ret = git_filter_global_init()) == 0 && (ret = git_merge_driver_global_init()) == 0 && (ret = git_transport_ssh_global_init()) == 0 && - (ret = git_openssl_stream_global_init()) == 0) + (ret = git_openssl_stream_global_init()) == 0 && + (ret = git_curl_stream_global_init()) == 0) ret = git_mwindow_global_init(); GIT_MEMORY_BARRIER; diff --git a/src/global.h b/src/global.h index 88f40aad1..3c0559c68 100644 --- a/src/global.h +++ b/src/global.h @@ -8,6 +8,7 @@ #define INCLUDE_global_h__ #include "common.h" + #include "mwindow.h" #include "hash.h" @@ -24,11 +25,6 @@ typedef struct { git_thread *current_thread; } git_global_st; -#ifdef GIT_OPENSSL -# include -extern SSL_CTX *git__ssl_ctx; -#endif - git_global_st *git__global_state(void); extern git_mutex git__mwindow_mutex; diff --git a/src/graph.c b/src/graph.c index 948f7d306..df82f0f71 100644 --- a/src/graph.c +++ b/src/graph.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include "revwalk.h" #include "merge.h" #include "git2/graph.h" diff --git a/src/hash.c b/src/hash.c index f3645a913..cc6676d4d 100644 --- a/src/hash.c +++ b/src/hash.c @@ -5,7 +5,6 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "hash.h" int git_hash_buf(git_oid *out, const void *data, size_t len) diff --git a/src/hash.h b/src/hash.h index 0db0339dc..31eaf8889 100644 --- a/src/hash.h +++ b/src/hash.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_hash_h__ #define INCLUDE_hash_h__ +#include "common.h" + #include "git2/oid.h" typedef struct git_hash_prov git_hash_prov; @@ -40,4 +42,4 @@ int git_hash_final(git_oid *out, git_hash_ctx *c); int git_hash_buf(git_oid *out, const void *data, size_t len); int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n); -#endif /* INCLUDE_hash_h__ */ +#endif diff --git a/src/hash/hash_collisiondetect.h b/src/hash/hash_collisiondetect.h index 5fdae8df6..4c5e3c302 100644 --- a/src/hash/hash_collisiondetect.h +++ b/src/hash/hash_collisiondetect.h @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_collisiondetect_h__ -#define INCLUDE_hash_collisiondetect_h__ +#ifndef INCLUDE_hash_hash_collisiondetect_h__ +#define INCLUDE_hash_hash_collisiondetect_h__ #include "hash.h" #include "sha1dc/sha1.h" @@ -44,4 +44,4 @@ GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) return 0; } -#endif /* INCLUDE_hash_collisiondetect_h__ */ +#endif diff --git a/src/hash/hash_common_crypto.h b/src/hash/hash_common_crypto.h index eeeddd0cc..5c3887dba 100644 --- a/src/hash/hash_common_crypto.h +++ b/src/hash/hash_common_crypto.h @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_common_crypto_h__ -#define INCLUDE_hash_common_crypto_h__ +#ifndef INCLUDE_hash_hash_common_crypto_h__ +#define INCLUDE_hash_hash_common_crypto_h__ #include "hash.h" @@ -16,6 +16,8 @@ struct git_hash_ctx { CC_SHA1_CTX c; }; +#define CC_LONG_MAX ((CC_LONG)-1) + #define git_hash_global_init() 0 #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) @@ -27,10 +29,21 @@ GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *_data, size_t len) { + const unsigned char *data = _data; + assert(ctx); - CC_SHA1_Update(&ctx->c, data, len); + + while (len > 0) { + CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; + + CC_SHA1_Update(&ctx->c, data, chunk); + + data += chunk; + len -= chunk; + } + return 0; } @@ -41,4 +54,4 @@ GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) return 0; } -#endif /* INCLUDE_hash_common_crypto_h__ */ +#endif diff --git a/src/hash/hash_generic.c b/src/hash/hash_generic.c index 472a7a696..7b33b6194 100644 --- a/src/hash/hash_generic.c +++ b/src/hash/hash_generic.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "hash_generic.h" + #include "hash.h" -#include "hash/hash_generic.h" #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h index daeb1cda8..21a042807 100644 --- a/src/hash/hash_generic.h +++ b/src/hash/hash_generic.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_generic_h__ -#define INCLUDE_hash_generic_h__ +#ifndef INCLUDE_hash_hash_generic_h__ +#define INCLUDE_hash_hash_generic_h__ + +#include "common.h" #include "hash.h" @@ -20,4 +22,4 @@ struct git_hash_ctx { #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) -#endif /* INCLUDE_hash_generic_h__ */ +#endif diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h index 9a55d472d..eb2dcb02f 100644 --- a/src/hash/hash_openssl.h +++ b/src/hash/hash_openssl.h @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_openssl_h__ -#define INCLUDE_hash_openssl_h__ +#ifndef INCLUDE_hash_hash_openssl_h__ +#define INCLUDE_hash_hash_openssl_h__ #include "hash.h" @@ -23,22 +23,37 @@ struct git_hash_ctx { GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) { assert(ctx); - SHA1_Init(&ctx->c); + + if (SHA1_Init(&ctx->c) != 1) { + giterr_set(GITERR_SHA1, "hash_openssl: failed to initialize hash context"); + return -1; + } + return 0; } GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) { assert(ctx); - SHA1_Update(&ctx->c, data, len); + + if (SHA1_Update(&ctx->c, data, len) != 1) { + giterr_set(GITERR_SHA1, "hash_openssl: failed to update hash"); + return -1; + } + return 0; } GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) { assert(ctx); - SHA1_Final(out->id, &ctx->c); + + if (SHA1_Final(out->id, &ctx->c) != 1) { + giterr_set(GITERR_SHA1, "hash_openssl: failed to finalize hash"); + return -1; + } + return 0; } -#endif /* INCLUDE_hash_openssl_h__ */ +#endif diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c index 6bae53e55..779802c4b 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/hash_win32.c @@ -5,10 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "hash_win32.h" + #include "global.h" #include "hash.h" -#include "hash/hash_win32.h" #include #include @@ -24,16 +24,20 @@ GIT_INLINE(int) hash_cng_prov_init(void) DWORD dll_path_len, size_len; /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - if (!git_has_win32_version(6, 0, 1)) + if (!git_has_win32_version(6, 0, 1)) { + giterr_set(GITERR_SHA1, "CryptoNG is not supported on this platform"); return -1; + } /* Load bcrypt.dll explicitly from the system directory */ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || StringCchCat(dll_path, MAX_PATH, "\\") < 0 || StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || - (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) + (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { + giterr_set(GITERR_SHA1, "CryptoNG library could not be loaded"); return -1; + } /* Load the function addresses */ if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || @@ -44,12 +48,16 @@ GIT_INLINE(int) hash_cng_prov_init(void) (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { FreeLibrary(hash_prov.prov.cng.dll); + + giterr_set(GITERR_OS, "CryptoNG functions could not be loaded"); return -1; } /* Load the SHA1 algorithm */ if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { FreeLibrary(hash_prov.prov.cng.dll); + + giterr_set(GITERR_OS, "algorithm provider could not be initialized"); return -1; } @@ -57,6 +65,8 @@ GIT_INLINE(int) hash_cng_prov_init(void) if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); FreeLibrary(hash_prov.prov.cng.dll); + + giterr_set(GITERR_OS, "algorithm handle could not be found"); return -1; } @@ -75,8 +85,10 @@ GIT_INLINE(void) hash_cng_prov_shutdown(void) /* Initialize CryptoAPI */ GIT_INLINE(int) hash_cryptoapi_prov_init() { - if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + giterr_set(GITERR_OS, "legacy hash context could not be started"); return -1; + } hash_prov.type = CRYPTOAPI; return 0; @@ -129,6 +141,7 @@ GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { ctx->ctx.cryptoapi.valid = 0; + giterr_set(GITERR_OS, "legacy hash implementation could not be created"); return -1; } @@ -136,12 +149,23 @@ GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len) +GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *_data, size_t len) { + const BYTE *data = (BYTE *)_data; + assert(ctx->ctx.cryptoapi.valid); - if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0)) - return -1; + while (len > 0) { + DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { + giterr_set(GITERR_OS, "legacy hash data could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } return 0; } @@ -153,8 +177,10 @@ GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx) assert(ctx->ctx.cryptoapi.valid); - if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0)) + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0)) { + giterr_set(GITERR_OS, "legacy hash data could not be finished"); error = -1; + } CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); ctx->ctx.cryptoapi.valid = 0; @@ -177,6 +203,8 @@ GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx) if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { git__free(ctx->ctx.cng.hash_object); + + giterr_set(GITERR_OS, "hash implementation could not be created"); return -1; } @@ -194,26 +222,41 @@ GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx) return 0; /* CNG needs to be finished to restart */ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { + giterr_set(GITERR_OS, "hash implementation could not be finished"); return -1; + } ctx->ctx.cng.updated = 0; return 0; } -GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len) +GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *_data, size_t len) { - if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0) - return -1; + PBYTE data = (PBYTE)_data; + + while (len > 0) { + ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; + + if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { + giterr_set(GITERR_OS, "hash could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } return 0; } GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx) { - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) { + giterr_set(GITERR_OS, "hash could not be finished"); return -1; + } ctx->ctx.cng.updated = 0; diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h index 2eee5ca79..9704204e2 100644 --- a/src/hash/hash_win32.h +++ b/src/hash/hash_win32.h @@ -5,10 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_win32_h__ -#define INCLUDE_hash_win32_h__ +#ifndef INCLUDE_hash_hash_win32_h__ +#define INCLUDE_hash_hash_win32_h__ #include "common.h" + #include "hash.h" #include @@ -137,4 +138,4 @@ struct git_hash_ctx { } ctx; }; -#endif /* INCLUDE_hash_openssl_h__ */ +#endif diff --git a/src/hashsig.c b/src/hashsig.c index bea538349..30d059463 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -4,6 +4,9 @@ * 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 "common.h" + #include "git2/sys/hashsig.h" #include "fileops.h" #include "util.h" diff --git a/src/ident.c b/src/ident.c index 4718ed664..7eccf9a43 100644 --- a/src/ident.c +++ b/src/ident.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include "git2/sys/filter.h" #include "filter.h" #include "buffer.h" diff --git a/src/idxmap.h b/src/idxmap.h index dc702c36e..f7e903a61 100644 --- a/src/idxmap.h +++ b/src/idxmap.h @@ -7,8 +7,9 @@ #ifndef INCLUDE_idxmap_h__ #define INCLUDE_idxmap_h__ -#include #include "common.h" + +#include #include "git2/index.h" #define kmalloc git__malloc diff --git a/src/ignore.c b/src/ignore.c index c324d4dd4..dddd7e81e 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,6 +1,14 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "ignore.h" + #include "git2/ignore.h" #include "common.h" -#include "ignore.h" #include "attrcache.h" #include "path.h" #include "config.h" @@ -40,38 +48,42 @@ */ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) { + int (*cmp)(const char *, const char *, size_t); git_attr_fnmatch *longer, *shorter; char *p; - if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0 - && (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) { + if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 + || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) + return false; - /* If lengths match we need to have an exact match */ - if (rule->length == neg->length) { - return strcmp(rule->pattern, neg->pattern) == 0; - } else if (rule->length < neg->length) { - shorter = rule; - longer = neg; - } else { - shorter = neg; - longer = rule; - } + if (neg->flags & GIT_ATTR_FNMATCH_ICASE) + cmp = git__strncasecmp; + else + cmp = git__strncmp; - /* Otherwise, we need to check if the shorter - * rule is a basename only (that is, it contains - * no path separator) and, if so, if it - * matches the tail of the longer rule */ - p = longer->pattern + longer->length - shorter->length; - - if (p[-1] != '/') - return false; - if (memchr(shorter->pattern, '/', shorter->length) != NULL) - return false; - - return memcmp(p, shorter->pattern, shorter->length) == 0; + /* If lengths match we need to have an exact match */ + if (rule->length == neg->length) { + return cmp(rule->pattern, neg->pattern, rule->length) == 0; + } else if (rule->length < neg->length) { + shorter = rule; + longer = neg; + } else { + shorter = neg; + longer = rule; } - return false; + /* Otherwise, we need to check if the shorter + * rule is a basename only (that is, it contains + * no path separator) and, if so, if it + * matches the tail of the longer rule */ + p = longer->pattern + longer->length - shorter->length; + + if (p[-1] != '/') + return false; + if (memchr(shorter->pattern, '/', shorter->length) != NULL) + return false; + + return cmp(p, shorter->pattern, shorter->length) == 0; } /** @@ -89,7 +101,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) */ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) { - int error = 0; + int error = 0, fnflags; size_t i; git_attr_fnmatch *rule; char *path; @@ -97,6 +109,10 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match *out = 0; + fnflags = FNM_PATHNAME; + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + fnflags |= FNM_IGNORECASE; + /* path of the file relative to the workdir, so we match the rules in subdirs */ if (match->containing_dir) { git_buf_puts(&buf, match->containing_dir); @@ -117,12 +133,12 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match continue; } - /* - * When dealing with a directory, we add '/' so - * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR - * alone isn't enough as that's also set for nagations, so we - * need to check that NEGATIVE is off. - */ + /* + * When dealing with a directory, we add '/' so + * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR + * alone isn't enough as that's also set for nagations, so we + * need to check that NEGATIVE is off. + */ git_buf_clear(&buf); if (rule->containing_dir) { git_buf_puts(&buf, rule->containing_dir); @@ -136,7 +152,7 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match if (error < 0) goto out; - if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) { + if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) { giterr_set(GITERR_INVALID, "error matching pattern"); goto out; } @@ -197,10 +213,26 @@ static int parse_ignore_file( if (ignore_case) match->flags |= GIT_ATTR_FNMATCH_ICASE; + while (match->length > 0) { + if (match->pattern[match->length - 1] == ' ' || + match->pattern[match->length - 1] == '\t') { + match->pattern[match->length - 1] = 0; + match->length --; + } else { + break; + } + } + scan = git__next_line(scan); - /* if a negative match doesn't actually do anything, throw it away */ - if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) + /* + * If a negative match doesn't actually do anything, + * throw it away. As we cannot always verify whether a + * rule containing wildcards negates another rule, we + * do not optimize away these rules, though. + * */ + if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE + && !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) error = does_negate_rule(&valid_rule, &attrs->rules, match); if (!error && valid_rule) @@ -508,6 +540,7 @@ int git_ignore_path_is_ignored( git_ignores ignores; unsigned int i; git_attr_file *file; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; assert(repo && ignored && pathname); @@ -516,7 +549,10 @@ int git_ignore_path_is_ignored( memset(&path, 0, sizeof(path)); memset(&ignores, 0, sizeof(ignores)); - if ((error = git_attr_path__init(&path, pathname, workdir, GIT_DIR_FLAG_UNKNOWN)) < 0 || + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) goto cleanup; diff --git a/src/ignore.h b/src/ignore.h index 876c8e0ea..5895d3faa 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_ignore_h__ #define INCLUDE_ignore_h__ +#include "common.h" + #include "repository.h" #include "vector.h" #include "attr_file.h" diff --git a/src/index.c b/src/index.c index c29e90fb0..a867547fb 100644 --- a/src/index.c +++ b/src/index.c @@ -5,11 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "index.h" + #include -#include "common.h" #include "repository.h" -#include "index.h" #include "tree.h" #include "tree-cache.h" #include "hash.h" @@ -1396,12 +1396,16 @@ static int index_conflict_to_reuc(git_index *index, const char *path) return ret; } -static bool valid_filemode(const int filemode) +GIT_INLINE(bool) is_file_or_link(const int filemode) { return (filemode == GIT_FILEMODE_BLOB || filemode == GIT_FILEMODE_BLOB_EXECUTABLE || - filemode == GIT_FILEMODE_LINK || - filemode == GIT_FILEMODE_COMMIT); + filemode == GIT_FILEMODE_LINK); +} + +GIT_INLINE(bool) valid_filemode(const int filemode) +{ + return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); } int git_index_add_frombuffer( @@ -1419,7 +1423,7 @@ int git_index_add_frombuffer( "could not initialize index entry. " "Index is not backed up by an existing repository."); - if (!valid_filemode(source_entry->mode)) { + if (!is_file_or_link(source_entry->mode)) { giterr_set(GITERR_INDEX, "invalid filemode"); return -1; } @@ -1605,7 +1609,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) assert(index && source_entry && source_entry->path); if (!valid_filemode(source_entry->mode)) { - giterr_set(GITERR_INDEX, "invalid filemode"); + giterr_set(GITERR_INDEX, "invalid entry mode"); return -1; } @@ -2295,8 +2299,9 @@ static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flag } } -static size_t read_entry( +static int read_entry( git_index_entry **out, + size_t *out_size, git_index *index, const void *buffer, size_t buffer_size, @@ -2310,7 +2315,7 @@ static size_t read_entry( char *tmp_path = NULL; if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size) - return 0; + return -1; /* buffer is not guaranteed to be aligned */ memcpy(&source, buffer, sizeof(struct entry_short)); @@ -2352,7 +2357,7 @@ static size_t read_entry( path_end = memchr(path_ptr, '\0', buffer_size); if (path_end == NULL) - return 0; + return -1; path_length = path_end - path_ptr; } @@ -2360,19 +2365,24 @@ static size_t read_entry( entry_size = index_entry_size(path_length, 0, entry.flags); entry.path = (char *)path_ptr; } else { - size_t varint_len; - size_t strip_len = git_decode_varint((const unsigned char *)path_ptr, - &varint_len); - size_t last_len = strlen(last); - size_t prefix_len = last_len - strip_len; - size_t suffix_len = strlen(path_ptr + varint_len); - size_t path_len; + size_t varint_len, last_len, prefix_len, suffix_len, path_len; + uintmax_t strip_len; - if (varint_len == 0) + strip_len = git_decode_varint((const unsigned char *)path_ptr, &varint_len); + last_len = strlen(last); + + if (varint_len == 0 || last_len < strip_len) return index_error_invalid("incorrect prefix length"); + prefix_len = last_len - strip_len; + suffix_len = strlen(path_ptr + varint_len); + GITERR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); GITERR_CHECK_ALLOC_ADD(&path_len, path_len, 1); + + if (path_len > GIT_PATH_MAX) + return index_error_invalid("unreasonable path length"); + tmp_path = git__malloc(path_len); GITERR_CHECK_ALLOC(tmp_path); @@ -2382,16 +2392,20 @@ static size_t read_entry( entry.path = tmp_path; } + if (entry_size == 0) + return -1; + if (INDEX_FOOTER_SIZE + entry_size > buffer_size) - return 0; + return -1; if (index_entry_dup(out, index, &entry) < 0) { git__free(tmp_path); - return 0; + return -1; } git__free(tmp_path); - return entry_size; + *out_size = entry_size; + return 0; } static int read_header(struct index_header *dest, const void *buffer) @@ -2494,11 +2508,10 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { - git_index_entry *entry; - size_t entry_size = read_entry(&entry, index, buffer, buffer_size, last); + git_index_entry *entry = NULL; + size_t entry_size; - /* 0 bytes read means an object corruption */ - if (entry_size == 0) { + if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) { error = index_error_invalid("invalid entry"); goto done; } diff --git a/src/index.h b/src/index.h index 9918f140d..0f1c0956c 100644 --- a/src/index.h +++ b/src/index.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_index_h__ #define INCLUDE_index_h__ +#include "common.h" + #include "fileops.h" #include "filebuf.h" #include "vector.h" diff --git a/src/indexer.c b/src/indexer.c index 15f6cc2c4..c0976f270 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -5,10 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "indexer.h" + #include "git2/indexer.h" #include "git2/object.h" -#include "common.h" #include "pack.h" #include "mwindow.h" #include "posix.h" @@ -186,13 +187,17 @@ static int store_delta(git_indexer *idx) return 0; } -static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type) +static int hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type) { char buffer[64]; size_t hdrlen; + int error; - hdrlen = git_odb__format_object_header(buffer, sizeof(buffer), (size_t)len, type); - git_hash_update(ctx, buffer, hdrlen); + if ((error = git_odb__format_object_header(&hdrlen, + buffer, sizeof(buffer), (size_t)len, type)) < 0) + return error; + + return git_hash_update(ctx, buffer, hdrlen); } static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) @@ -620,7 +625,10 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_tran idx->have_delta = 1; } else { idx->have_delta = 0; - hash_header(&idx->hash_ctx, entry_size, type); + + error = hash_header(&idx->hash_ctx, entry_size, type); + if (error < 0) + goto on_error; } idx->have_stream = 1; @@ -843,6 +851,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats) { unsigned int i; + int error; struct delta_info *delta; int progressed = 0, non_null = 0, progress_cb_result; @@ -857,8 +866,13 @@ static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats) non_null = 1; idx->off = delta->delta_off; - if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) - continue; + if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) { + if (error == GIT_PASSTHROUGH) { + /* We have not seen the base object, we'll try again later. */ + continue; + } + return -1; + } if (hash_and_save(idx, &obj, delta->delta_off) < 0) continue; @@ -950,6 +964,10 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack"); return -1; } + if (idx->off + 20 > idx->pack->mwf.size) { + giterr_set(GITERR_INDEXER, "missing trailer at the end of the pack"); + return -1; + } packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); if (packfile_trailer == NULL) { @@ -1118,6 +1136,9 @@ void git_indexer_free(git_indexer *idx) if (idx == NULL) return; + if (idx->have_stream) + git_packfile_stream_free(&idx->stream); + git_vector_free_deep(&idx->objects); if (idx->pack->idx_cache) { diff --git a/src/indexer.h b/src/indexer.h index 702694bbf..8ee6115a6 100644 --- a/src/indexer.h +++ b/src/indexer.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_indexer_h__ #define INCLUDE_indexer_h__ -extern int git_indexer__set_fsync(git_indexer *idx, int do_fsync); +#include "common.h" + +#include "git2/indexer.h" + +extern void git_indexer__set_fsync(git_indexer *idx, int do_fsync); #endif diff --git a/src/integer.h b/src/integer.h index 61712cebf..30528db59 100644 --- a/src/integer.h +++ b/src/integer.h @@ -93,4 +93,4 @@ GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t tw #endif -#endif /* INCLUDE_integer_h__ */ +#endif diff --git a/src/iterator.c b/src/iterator.c index 8fc62c01c..ff075ce8c 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -6,6 +6,7 @@ */ #include "iterator.h" + #include "tree.h" #include "index.h" @@ -22,6 +23,7 @@ #define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) #define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) #define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) +#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) @@ -1456,10 +1458,15 @@ static void filesystem_iterator_set_current( filesystem_iterator_entry *entry) { iter->entry.ctime.seconds = entry->st.st_ctime; - iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; - iter->entry.mtime.seconds = entry->st.st_mtime; + +#if defined(GIT_USE_NSEC) + iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; +#else + iter->entry.ctime.nanoseconds = 0; + iter->entry.mtime.nanoseconds = 0; +#endif iter->entry.dev = entry->st.st_dev; iter->entry.ino = entry->st.st_ino; @@ -1490,10 +1497,41 @@ static int filesystem_iterator_current( return 0; } +static int filesystem_iterator_is_dir( + bool *is_dir, + const filesystem_iterator *iter, + const filesystem_iterator_entry *entry) +{ + struct stat st; + git_buf fullpath = GIT_BUF_INIT; + int error = 0; + + if (S_ISDIR(entry->st.st_mode)) { + *is_dir = 1; + goto done; + } + + if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { + *is_dir = 0; + goto done; + } + + if ((error = git_buf_joinpath(&fullpath, iter->root, entry->path)) < 0 || + (error = p_stat(fullpath.ptr, &st)) < 0) + goto done; + + *is_dir = S_ISDIR(st.st_mode); + +done: + git_buf_free(&fullpath); + return error; +} + static int filesystem_iterator_advance( const git_index_entry **out, git_iterator *i) { filesystem_iterator *iter = (filesystem_iterator *)i; + bool is_dir; int error = 0; iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; @@ -1518,7 +1556,10 @@ static int filesystem_iterator_advance( entry = frame->entries.contents[frame->next_idx]; frame->next_idx++; - if (S_ISDIR(entry->st.st_mode)) { + if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) + break; + + if (is_dir) { if (iterator__do_autoexpand(iter)) { error = filesystem_iterator_frame_push(iter, entry); diff --git a/src/iterator.h b/src/iterator.h index 0b239a5bd..a6497d87b 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -8,6 +8,7 @@ #define INCLUDE_iterator_h__ #include "common.h" + #include "git2/index.h" #include "vector.h" #include "buffer.h" @@ -38,6 +39,8 @@ typedef enum { GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), /** include conflicts */ GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), + /** descend into symlinked directories */ + GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7), } git_iterator_flag_t; typedef enum { diff --git a/src/map.h b/src/map.h index da3d1e19a..2009c3ab5 100644 --- a/src/map.h +++ b/src/map.h @@ -43,4 +43,4 @@ typedef struct { /* memory mapped buffer */ extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); extern int p_munmap(git_map *map); -#endif /* INCLUDE_map_h__ */ +#endif diff --git a/src/merge.c b/src/merge.c index 6e00b5adb..6c98d13fb 100644 --- a/src/merge.c +++ b/src/merge.c @@ -5,13 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "merge.h" + #include "posix.h" #include "buffer.h" #include "repository.h" #include "revwalk.h" #include "commit_list.h" -#include "merge.h" #include "path.h" #include "refs.h" #include "object.h" @@ -32,6 +32,8 @@ #include "commit.h" #include "oidarray.h" #include "merge_driver.h" +#include "oidmap.h" +#include "array.h" #include "git2/types.h" #include "git2/repository.h" @@ -1005,27 +1007,6 @@ struct merge_diff_similarity { size_t other_idx; }; -static int index_entry_similarity_exact( - git_repository *repo, - git_index_entry *a, - size_t a_idx, - git_index_entry *b, - size_t b_idx, - void **cache, - const git_merge_options *opts) -{ - GIT_UNUSED(repo); - GIT_UNUSED(a_idx); - GIT_UNUSED(b_idx); - GIT_UNUSED(cache); - GIT_UNUSED(opts); - - if (git_oid__cmp(&a->id, &b->id) == 0) - return 100; - - return 0; -} - static int index_entry_similarity_calc( void **out, git_repository *repo, @@ -1102,12 +1083,154 @@ static int index_entry_similarity_inexact( return score; } -static int merge_diff_mark_similarity( +/* Tracks deletes by oid for merge_diff_mark_similarity_exact(). This is a +* non-shrinking queue where next_pos is the next position to dequeue. +*/ +typedef struct { + git_array_t(size_t) arr; + size_t next_pos; + size_t first_entry; +} deletes_by_oid_queue; + +static void deletes_by_oid_free(git_oidmap *map) { + deletes_by_oid_queue *queue; + + if (!map) + return; + + git_oidmap_foreach_value(map, queue, { + git_array_clear(queue->arr); + }); + git_oidmap_free(map); +} + +static int deletes_by_oid_enqueue(git_oidmap *map, git_pool* pool, const git_oid *id, size_t idx) { + khint_t pos; + deletes_by_oid_queue *queue; + size_t *array_entry; + int error; + + pos = git_oidmap_lookup_index(map, id); + if (!git_oidmap_valid_index(map, pos)) { + queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); + GITERR_CHECK_ALLOC(queue); + + git_array_init(queue->arr); + queue->next_pos = 0; + queue->first_entry = idx; + + git_oidmap_insert(map, id, queue, &error); + if (error < 0) + return -1; + } else { + queue = git_oidmap_value_at(map, pos); + array_entry = git_array_alloc(queue->arr); + GITERR_CHECK_ALLOC(array_entry); + *array_entry = idx; + } + + return 0; +} + +static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) { + khint_t pos; + deletes_by_oid_queue *queue; + size_t *array_entry; + + pos = git_oidmap_lookup_index(map, id); + + if (!git_oidmap_valid_index(map, pos)) + return GIT_ENOTFOUND; + + queue = git_oidmap_value_at(map, pos); + + if (queue->next_pos == 0) { + *idx = queue->first_entry; + } else { + array_entry = git_array_get(queue->arr, queue->next_pos - 1); + if (array_entry == NULL) + return GIT_ENOTFOUND; + + *idx = *array_entry; + } + + queue->next_pos++; + return 0; +} + +static int merge_diff_mark_similarity_exact( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs) +{ + size_t i, j; + git_merge_diff *conflict_src, *conflict_tgt; + git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; + int error = 0; + + if (!(ours_deletes_by_oid = git_oidmap_alloc()) || + !(theirs_deletes_by_oid = git_oidmap_alloc())) { + error = -1; + goto done; + } + + /* Build a map of object ids to conflicts */ + git_vector_foreach(&diff_list->conflicts, i, conflict_src) { + /* Items can be the source of a rename iff they have an item in the + * ancestor slot and lack an item in the ours or theirs slot. */ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) + continue; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { + error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { + error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + } + + git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) + continue; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { + if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { + similarity_ours[i].similarity = 100; + similarity_ours[i].other_idx = j; + + similarity_ours[j].similarity = 100; + similarity_ours[j].other_idx = i; + } + } + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { + if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { + similarity_theirs[i].similarity = 100; + similarity_theirs[i].other_idx = j; + + similarity_theirs[j].similarity = 100; + similarity_theirs[j].other_idx = i; + } + } + } + +done: + deletes_by_oid_free(ours_deletes_by_oid); + deletes_by_oid_free(theirs_deletes_by_oid); + + return error; +} + +static int merge_diff_mark_similarity_inexact( git_repository *repo, git_merge_diff_list *diff_list, struct merge_diff_similarity *similarity_ours, struct merge_diff_similarity *similarity_theirs, - int (*similarity_fn)(git_repository *, git_index_entry *, size_t, git_index_entry *, size_t, void **, const git_merge_options *), void **cache, const git_merge_options *opts) { @@ -1132,7 +1255,7 @@ static int merge_diff_mark_similarity( if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); if (similarity == GIT_EBUFS) continue; @@ -1158,7 +1281,7 @@ static int merge_diff_mark_similarity( if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); if (similarity > similarity_theirs[i].similarity && similarity > similarity_theirs[j].similarity) { @@ -1396,11 +1519,10 @@ int git_merge_diff_list__find_renames( /* Calculate similarity between items that were deleted from the ancestor * and added in the other branch. */ - if ((error = merge_diff_mark_similarity(repo, diff_list, similarity_ours, - similarity_theirs, index_entry_similarity_exact, NULL, opts)) < 0) + if ((error = merge_diff_mark_similarity_exact(diff_list, similarity_ours, similarity_theirs)) < 0) goto done; - if (diff_list->conflicts.length <= opts->target_limit) { + if (opts->rename_threshold < 100 && diff_list->conflicts.length <= opts->target_limit) { GITERR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); cache = git__calloc(cache_size, sizeof(void *)); GITERR_CHECK_ALLOC(cache); @@ -1410,9 +1532,8 @@ int git_merge_diff_list__find_renames( if (src_count > opts->target_limit || tgt_count > opts->target_limit) { /* TODO: report! */ } else { - if ((error = merge_diff_mark_similarity( - repo, diff_list, similarity_ours, similarity_theirs, - index_entry_similarity_inexact, cache, opts)) < 0) + if ((error = merge_diff_mark_similarity_inexact( + repo, diff_list, similarity_ours, similarity_theirs, cache, opts)) < 0) goto done; } } @@ -1954,6 +2075,7 @@ int git_merge__iterators( file_opts.our_label = "Temporary merge branch 1"; file_opts.their_label = "Temporary merge branch 2"; file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED; + file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2; } diff_list = git_merge_diff_list__alloc(repo); @@ -2141,7 +2263,7 @@ static int compute_base( git_oidarray bases = {0}; git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - size_t i; + size_t i, base_count; int error; *out = NULL; @@ -2149,17 +2271,27 @@ static int compute_base( if (given_opts) memcpy(&opts, given_opts, sizeof(git_merge_options)); - if ((error = insert_head_ids(&head_ids, one)) < 0 || - (error = insert_head_ids(&head_ids, two)) < 0) + /* With more than two commits, merge_bases_many finds the base of + * the first commit and a hypothetical merge of the others. Since + * "one" may itself be a virtual commit, which insert_head_ids + * substitutes multiple ancestors for, it needs to be added + * after "two" which is always a single real commit. + */ + if ((error = insert_head_ids(&head_ids, two)) < 0 || + (error = insert_head_ids(&head_ids, one)) < 0 || + (error = git_merge_bases_many(&bases, repo, + head_ids.size, head_ids.ptr)) < 0) goto done; - if ((error = git_merge_bases_many(&bases, repo, - head_ids.size, head_ids.ptr)) < 0 || - (error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0 || - (opts.flags & GIT_MERGE_NO_RECURSIVE)) + base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; + + if (base_count) + git_oidarray__reverse(&bases); + + if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) goto done; - for (i = 1; i < bases.count; i++) { + for (i = 1; i < base_count; i++) { recursion_level++; if (opts.recursion_limit && recursion_level > opts.recursion_limit) diff --git a/src/merge.h b/src/merge.h index f8cac161f..173a1b435 100644 --- a/src/merge.h +++ b/src/merge.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_merge_h__ #define INCLUDE_merge_h__ +#include "common.h" + #include "vector.h" #include "commit_list.h" #include "pool.h" diff --git a/src/merge_driver.c b/src/merge_driver.c index 0f35d23c2..ea4bd2787 100644 --- a/src/merge_driver.c +++ b/src/merge_driver.c @@ -5,11 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "merge_driver.h" + #include "vector.h" #include "global.h" #include "merge.h" -#include "merge_driver.h" #include "git2/merge.h" #include "git2/sys/merge.h" diff --git a/src/merge_driver.h b/src/merge_driver.h index bde27502c..6b7da5287 100644 --- a/src/merge_driver.h +++ b/src/merge_driver.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_merge_driver_h__ #define INCLUDE_merge_driver_h__ +#include "common.h" + #include "git2/merge.h" #include "git2/index.h" #include "git2/sys/merge.h" diff --git a/src/merge_file.c b/src/merge_file.c index 5ecd8f4d0..a36c1986c 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "repository.h" #include "posix.h" #include "fileops.h" @@ -125,6 +126,8 @@ static int merge_file__xdiff( if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) xmparam.xpp.flags |= XDF_NEED_MINIMAL; + xmparam.marker_size = options.marker_size; + if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, &their_mmfile, &xmparam, &mmbuffer)) < 0) { giterr_set(GITERR_MERGE, "failed to merge files"); diff --git a/src/message.h b/src/message.h index 3c4b8dc45..251727b22 100644 --- a/src/message.h +++ b/src/message.h @@ -7,9 +7,11 @@ #ifndef INCLUDE_message_h__ #define INCLUDE_message_h__ +#include "common.h" + #include "git2/message.h" #include "buffer.h" int git_message__prettify(git_buf *message_out, const char *message, int strip_comments); -#endif /* INCLUDE_message_h__ */ +#endif diff --git a/src/mwindow.c b/src/mwindow.c index 7bb9dbbe2..38d0352b4 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "mwindow.h" + #include "vector.h" #include "fileops.h" #include "map.h" diff --git a/src/mwindow.h b/src/mwindow.h index bdde9e0a4..ea962d1b6 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -8,6 +8,8 @@ #ifndef INCLUDE_mwindow__ #define INCLUDE_mwindow__ +#include "common.h" + #include "map.h" #include "vector.h" diff --git a/src/netops.c b/src/netops.c index 4b73baa0e..fa20cbaf2 100644 --- a/src/netops.c +++ b/src/netops.c @@ -5,11 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "netops.h" + #include #include "git2/errors.h" -#include "common.h" -#include "netops.h" #include "posix.h" #include "buffer.h" #include "http_parser.h" @@ -206,83 +206,96 @@ void gitno_connection_data_free_ptrs(gitno_connection_data *d) git__free(d->pass); d->pass = NULL; } -#define hex2c(c) ((c | 32) % 39 - 9) -static char* unescape(char *str) -{ - int x, y; - int len = (int)strlen(str); - - for (x=y=0; str[y]; ++x, ++y) { - if ((str[x] = str[y]) == '%') { - if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) { - str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]); - y += 2; - } - } - } - str[x] = '\0'; - return str; -} - int gitno_extract_url_parts( - char **host, - char **port, - char **path, - char **username, - char **password, - const char *url, - const char *default_port) + char **host_out, + char **port_out, + char **path_out, + char **username_out, + char **password_out, + const char *url, + const char *default_port) { struct http_parser_url u = {0}; - const char *_host, *_port, *_path, *_userinfo; + bool has_host, has_port, has_path, has_userinfo; + git_buf host = GIT_BUF_INIT, + port = GIT_BUF_INIT, + path = GIT_BUF_INIT, + username = GIT_BUF_INIT, + password = GIT_BUF_INIT; + int error = 0; if (http_parser_parse_url(url, strlen(url), false, &u)) { giterr_set(GITERR_NET, "malformed URL '%s'", url); - return GIT_EINVALIDSPEC; + error = GIT_EINVALIDSPEC; + goto done; } - _host = url+u.field_data[UF_HOST].off; - _port = url+u.field_data[UF_PORT].off; - _path = url+u.field_data[UF_PATH].off; - _userinfo = url+u.field_data[UF_USERINFO].off; + has_host = !!(u.field_set & (1 << UF_HOST)); + has_port = !!(u.field_set & (1 << UF_PORT)); + has_path = !!(u.field_set & (1 << UF_PATH)); + has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); - if (u.field_set & (1 << UF_HOST)) { - *host = git__substrdup(_host, u.field_data[UF_HOST].len); - GITERR_CHECK_ALLOC(*host); + if (has_host) { + const char *url_host = url + u.field_data[UF_HOST].off; + size_t url_host_len = u.field_data[UF_HOST].len; + git_buf_decode_percent(&host, url_host, url_host_len); } - if (u.field_set & (1 << UF_PORT)) - *port = git__substrdup(_port, u.field_data[UF_PORT].len); - else - *port = git__strdup(default_port); - GITERR_CHECK_ALLOC(*port); - - if (path) { - if (u.field_set & (1 << UF_PATH)) { - *path = git__substrdup(_path, u.field_data[UF_PATH].len); - GITERR_CHECK_ALLOC(*path); - } else { - git__free(*port); - *port = NULL; - git__free(*host); - *host = NULL; - giterr_set(GITERR_NET, "invalid url, missing path"); - return GIT_EINVALIDSPEC; - } + if (has_port) { + const char *url_port = url + u.field_data[UF_PORT].off; + size_t url_port_len = u.field_data[UF_PORT].len; + git_buf_put(&port, url_port, url_port_len); + } else { + git_buf_puts(&port, default_port); } - if (u.field_set & (1 << UF_USERINFO)) { - const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len); + if (has_path && path_out) { + const char *url_path = url + u.field_data[UF_PATH].off; + size_t url_path_len = u.field_data[UF_PATH].len; + git_buf_decode_percent(&path, url_path, url_path_len); + } else if (path_out) { + giterr_set(GITERR_NET, "invalid url, missing path"); + error = GIT_EINVALIDSPEC; + goto done; + } + + if (has_userinfo) { + const char *url_userinfo = url + u.field_data[UF_USERINFO].off; + size_t url_userinfo_len = u.field_data[UF_USERINFO].len; + const char *colon = memchr(url_userinfo, ':', url_userinfo_len); + if (colon) { - *username = unescape(git__substrdup(_userinfo, colon - _userinfo)); - *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo))); - GITERR_CHECK_ALLOC(*password); - } else { - *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len); - } - GITERR_CHECK_ALLOC(*username); + const char *url_username = url_userinfo; + size_t url_username_len = colon - url_userinfo; + const char *url_password = colon + 1; + size_t url_password_len = url_userinfo_len - (url_username_len + 1); + git_buf_decode_percent(&username, url_username, url_username_len); + git_buf_decode_percent(&password, url_password, url_password_len); + } else { + git_buf_decode_percent(&username, url_userinfo, url_userinfo_len); + } } - return 0; + if (git_buf_oom(&host) || + git_buf_oom(&port) || + git_buf_oom(&path) || + git_buf_oom(&username) || + git_buf_oom(&password)) + return -1; + + *host_out = git_buf_detach(&host); + *port_out = git_buf_detach(&port); + if (path_out) + *path_out = git_buf_detach(&path); + *username_out = git_buf_detach(&username); + *password_out = git_buf_detach(&password); + +done: + git_buf_free(&host); + git_buf_free(&port); + git_buf_free(&path); + git_buf_free(&username); + git_buf_free(&password); + return error; } diff --git a/src/netops.h b/src/netops.h index b7170a0f2..75fd9a512 100644 --- a/src/netops.h +++ b/src/netops.h @@ -7,8 +7,9 @@ #ifndef INCLUDE_netops_h__ #define INCLUDE_netops_h__ -#include "posix.h" #include "common.h" + +#include "posix.h" #include "stream.h" #ifdef GIT_OPENSSL diff --git a/src/notes.c b/src/notes.c index 75108b9c9..f63ef3667 100644 --- a/src/notes.c +++ b/src/notes.c @@ -268,7 +268,9 @@ static int insert_note_in_tree_enotfound_cb(git_tree **out, GIT_FILEMODE_BLOB); } -static int note_write(git_oid *out, +static int note_write( + git_oid *notes_commit_out, + git_oid *notes_blob_out, git_repository *repo, const git_signature *author, const git_signature *committer, @@ -294,13 +296,17 @@ static int note_write(git_oid *out, insert_note_in_tree_enotfound_cb)) < 0) goto cleanup; - if (out) - git_oid_cpy(out, &oid); + if (notes_blob_out) + git_oid_cpy(notes_blob_out, &oid); + error = git_commit_create(&oid, repo, notes_ref, author, committer, NULL, GIT_NOTES_DEFAULT_MSG_ADD, tree, *parents == NULL ? 0 : 1, (const git_commit **) parents); + if (notes_commit_out) + git_oid_cpy(notes_commit_out, &oid); + cleanup: git_tree_free(tree); return error; @@ -363,7 +369,9 @@ cleanup: return error; } -static int note_remove(git_repository *repo, +static int note_remove( + git_oid *notes_commit_out, + git_repository *repo, const git_signature *author, const git_signature *committer, const char *notes_ref, git_tree *tree, const char *target, git_commit **parents) @@ -383,6 +391,12 @@ static int note_remove(git_repository *repo, *parents == NULL ? 0 : 1, (const git_commit **) parents); + if (error < 0) + goto cleanup; + + if (notes_commit_out) + git_oid_cpy(notes_commit_out, &oid); + cleanup: git_tree_free(tree_after_removal); return error; @@ -410,8 +424,7 @@ static int normalize_namespace(char **out, git_repository *repo, const char *not return note_get_default_ref(out, repo); } -static int retrieve_note_tree_and_commit( - git_tree **tree_out, +static int retrieve_note_commit( git_commit **commit_out, char **notes_ref_out, git_repository *repo, @@ -429,34 +442,82 @@ static int retrieve_note_tree_and_commit( if (git_commit_lookup(commit_out, repo, &oid) < 0) return error; - if ((error = git_commit_tree(tree_out, *commit_out)) < 0) - return error; - return 0; } +int git_note_commit_read( + git_note **out, + git_repository *repo, + git_commit *notes_commit, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_lookup(out, repo, notes_commit, tree, target); + +cleanup: + git_tree_free(tree); + return error; +} + int git_note_read(git_note **out, git_repository *repo, const char *notes_ref_in, const git_oid *oid) { int error; - char *target = NULL, *notes_ref = NULL; - git_tree *tree = NULL; + char *notes_ref = NULL; git_commit *commit = NULL; - target = git_oid_allocfmt(oid); - GITERR_CHECK_ALLOC(target); + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); - if (!(error = retrieve_note_tree_and_commit( - &tree, &commit, ¬es_ref, repo, notes_ref_in))) - error = note_lookup(out, repo, commit, tree, target); + if (error < 0) + goto cleanup; + error = git_note_commit_read(out, repo, commit, oid); + +cleanup: git__free(notes_ref); - git__free(target); - git_tree_free(tree); git_commit_free(commit); return error; } +int git_note_commit_create( + git_oid *notes_commit_out, + git_oid *notes_blob_out, + git_repository *repo, + git_commit *parent, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if (parent != NULL && (error = git_commit_tree(&tree, parent)) < 0) + goto cleanup; + + error = note_write(notes_commit_out, notes_blob_out, repo, author, + committer, NULL, note, tree, target, &parent, allow_note_overwrite); + + if (error < 0) + goto cleanup; + +cleanup: + git_tree_free(tree); + return error; +} + int git_note_create( git_oid *out, git_repository *repo, @@ -468,25 +529,59 @@ int git_note_create( int allow_note_overwrite) { int error; - char *target = NULL, *notes_ref = NULL; - git_commit *commit = NULL; - git_tree *tree = NULL; + char *notes_ref = NULL; + git_commit *existing_notes_commit = NULL; + git_reference *ref = NULL; + git_oid notes_blob_oid, notes_commit_oid; - target = git_oid_allocfmt(oid); - GITERR_CHECK_ALLOC(target); - - error = retrieve_note_tree_and_commit(&tree, &commit, ¬es_ref, repo, notes_ref_in); + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref, + repo, notes_ref_in); if (error < 0 && error != GIT_ENOTFOUND) goto cleanup; - error = note_write(out, repo, author, committer, notes_ref, - note, tree, target, &commit, allow_note_overwrite); + error = git_note_commit_create(¬es_commit_oid, + ¬es_blob_oid, + repo, existing_notes_commit, author, + committer, oid, note, + allow_note_overwrite); + if (error < 0) + goto cleanup; + + error = git_reference_create(&ref, repo, notes_ref, + ¬es_commit_oid, 1, NULL); + + if (out != NULL) + git_oid_cpy(out, ¬es_blob_oid); cleanup: git__free(notes_ref); - git__free(target); - git_commit_free(commit); + git_commit_free(existing_notes_commit); + git_reference_free(ref); + return error; +} + +int git_note_commit_remove( + git_oid *notes_commit_out, + git_repository *repo, + git_commit *notes_commit, + const git_signature *author, + const git_signature *committer, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_remove(notes_commit_out, + repo, author, committer, NULL, tree, target, ¬es_commit); + +cleanup: git_tree_free(tree); return error; } @@ -496,22 +591,29 @@ int git_note_remove(git_repository *repo, const char *notes_ref_in, const git_oid *oid) { int error; - char *target = NULL, *notes_ref; - git_commit *commit = NULL; - git_tree *tree = NULL; + char *notes_ref_target = NULL; + git_commit *existing_notes_commit = NULL; + git_oid new_notes_commit; + git_reference *notes_ref = NULL; - target = git_oid_allocfmt(oid); - GITERR_CHECK_ALLOC(target); + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref_target, + repo, notes_ref_in); - if (!(error = retrieve_note_tree_and_commit( - &tree, &commit, ¬es_ref, repo, notes_ref_in))) - error = note_remove( - repo, author, committer, notes_ref, tree, target, &commit); + if (error < 0) + goto cleanup; - git__free(notes_ref); - git__free(target); - git_commit_free(commit); - git_tree_free(tree); + error = git_note_commit_remove(&new_notes_commit, repo, + existing_notes_commit, author, committer, oid); + if (error < 0) + goto cleanup; + + error = git_reference_create(¬es_ref, repo, notes_ref_target, + &new_notes_commit, 1, NULL); + +cleanup: + git__free(notes_ref_target); + git_reference_free(notes_ref); + git_commit_free(existing_notes_commit); return error; } @@ -639,7 +741,6 @@ int git_note_foreach( return error; } - void git_note_iterator_free(git_note_iterator *it) { if (it == NULL) @@ -648,6 +749,24 @@ void git_note_iterator_free(git_note_iterator *it) git_iterator_free(it); } +int git_note_commit_iterator_new( + git_note_iterator **it, + git_commit *notes_commit) +{ + int error; + git_tree *tree; + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) + git_iterator_free(*it); + +cleanup: + git_tree_free(tree); + + return error; +} int git_note_iterator_new( git_note_iterator **it, @@ -656,19 +775,16 @@ int git_note_iterator_new( { int error; git_commit *commit = NULL; - git_tree *tree = NULL; char *notes_ref; - error = retrieve_note_tree_and_commit(&tree, &commit, ¬es_ref, repo, notes_ref_in); + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); if (error < 0) goto cleanup; - if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) - git_iterator_free(*it); + error = git_note_commit_iterator_new(it, commit); cleanup: git__free(notes_ref); - git_tree_free(tree); git_commit_free(commit); return error; diff --git a/src/notes.h b/src/notes.h index cfc0ca239..2168e4595 100644 --- a/src/notes.h +++ b/src/notes.h @@ -29,4 +29,4 @@ struct git_note { char *message; }; -#endif /* INCLUDE_notes_h__ */ +#endif diff --git a/src/object.c b/src/object.c index 2da36a2ee..48f561384 100644 --- a/src/object.c +++ b/src/object.c @@ -4,9 +4,11 @@ * 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 "object.h" + #include "git2/object.h" -#include "common.h" #include "repository.h" #include "commit.h" @@ -233,14 +235,23 @@ const char *git_object_type2string(git_otype type) } git_otype git_object_string2type(const char *str) +{ + if (!str) + return GIT_OBJ_BAD; + + return git_object_stringn2type(str, strlen(str)); +} + +git_otype git_object_stringn2type(const char *str, size_t len) { size_t i; - if (!str || !*str) + if (!str || !len || !*str) return GIT_OBJ_BAD; for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) - if (!strcmp(str, git_objects_table[i].str)) + if (*git_objects_table[i].str && + !git__prefixncmp(str, len, git_objects_table[i].str)) return (git_otype)i; return GIT_OBJ_BAD; diff --git a/src/object.h b/src/object.h index dd227d16d..e46c9cafa 100644 --- a/src/object.h +++ b/src/object.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_object_h__ #define INCLUDE_object_h__ +#include "common.h" + #include "repository.h" extern bool git_object__strict_input_validation; @@ -28,6 +30,8 @@ int git_object__from_odb_object( int git_object__resolve_to_type(git_object **obj, git_otype type); +git_otype git_object_stringn2type(const char *str, size_t len); + int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); diff --git a/src/object_api.c b/src/object_api.c index e0d8760e7..75efa4d10 100644 --- a/src/object_api.c +++ b/src/object_api.c @@ -4,11 +4,12 @@ * 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 "git2/object.h" #include "common.h" -#include "repository.h" +#include "git2/object.h" + +#include "repository.h" #include "commit.h" #include "tree.h" #include "blob.h" diff --git a/src/odb.c b/src/odb.c index ae8f247e3..ef9c87555 100644 --- a/src/odb.c +++ b/src/odb.c @@ -5,13 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "odb.h" + #include #include "git2/object.h" #include "git2/sys/odb_backend.h" #include "fileops.h" #include "hash.h" -#include "odb.h" #include "delta.h" #include "filter.h" #include "repository.h" @@ -53,6 +53,7 @@ static git_cache *odb_cache(git_odb *odb) static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id); static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); +static int error_null_oid(int error, const char *message); static git_otype odb_hardcoded_type(const git_oid *id) { @@ -65,50 +66,75 @@ static git_otype odb_hardcoded_type(const git_oid *id) return GIT_OBJ_BAD; } -static int odb_read_hardcoded(git_rawobj *raw, const git_oid *id) +static int odb_read_hardcoded(bool *found, git_rawobj *raw, const git_oid *id) { - git_otype type = odb_hardcoded_type(id); - if (type == GIT_OBJ_BAD) - return -1; + git_otype type; + + *found = false; + + if ((type = odb_hardcoded_type(id)) == GIT_OBJ_BAD) + return 0; raw->type = type; raw->len = 0; raw->data = git__calloc(1, sizeof(uint8_t)); + GITERR_CHECK_ALLOC(raw->data); + + *found = true; return 0; } -int git_odb__format_object_header(char *hdr, size_t n, git_off_t obj_len, git_otype obj_type) +int git_odb__format_object_header( + size_t *written, + char *hdr, + size_t hdr_size, + git_off_t obj_len, + git_otype obj_type) { const char *type_str = git_object_type2string(obj_type); - int len = p_snprintf(hdr, n, "%s %lld", type_str, (long long)obj_len); - assert(len > 0 && len <= (int)n); - return len+1; + int hdr_max = (hdr_size > INT_MAX-2) ? (INT_MAX-2) : (int)hdr_size; + int len; + + len = p_snprintf(hdr, hdr_max, "%s %lld", type_str, (long long)obj_len); + + if (len < 0 || len >= hdr_max) { + giterr_set(GITERR_OS, "object header creation failed"); + return -1; + } + + *written = (size_t)(len + 1); + return 0; } int git_odb__hashobj(git_oid *id, git_rawobj *obj) { git_buf_vec vec[2]; char header[64]; - int hdrlen; + size_t hdrlen; + int error; assert(id && obj); - if (!git_object_typeisloose(obj->type)) + if (!git_object_typeisloose(obj->type)) { + giterr_set(GITERR_INVALID, "invalid object type"); return -1; + } - if (!obj->data && obj->len != 0) + if (!obj->data && obj->len != 0) { + giterr_set(GITERR_INVALID, "invalid object"); return -1; + } - hdrlen = git_odb__format_object_header(header, sizeof(header), obj->len, obj->type); + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), obj->len, obj->type)) < 0) + return error; vec[0].data = header; vec[0].len = hdrlen; vec[1].data = obj->data; vec[1].len = obj->len; - git_hash_vec(id, vec, 2); - - return 0; + return git_hash_vec(id, vec, 2); } @@ -171,7 +197,7 @@ void git_odb_object_free(git_odb_object *object) int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) { - int hdr_len; + size_t hdr_len; char hdr[64], buffer[FILEIO_BUFSIZE]; git_hash_ctx ctx; ssize_t read_len = 0; @@ -183,9 +209,11 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) } if ((error = git_hash_ctx_init(&ctx)) < 0) - return -1; + return error; - hdr_len = git_odb__format_object_header(hdr, sizeof(hdr), size, type); + if ((error = git_odb__format_object_header(&hdr_len, hdr, + sizeof(hdr), size, type)) < 0) + goto done; if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) goto done; @@ -341,8 +369,7 @@ static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t { fake_wstream *stream = (fake_wstream *)_stream; - if (stream->written + len > stream->size) - return -1; + assert(stream->written + len <= stream->size); memcpy(stream->buffer + stream->written, data, len); stream->written += len; @@ -735,6 +762,9 @@ int git_odb_exists(git_odb *db, const git_oid *id) assert(db && id); + if (git_oid_iszero(id)) + return 0; + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { git_odb_object_free(object); return 1; @@ -796,7 +826,7 @@ int git_odb_exists_prefix( git_oid *out, git_odb *db, const git_oid *short_id, size_t len) { int error; - git_oid key = {{0}}; + git_oid key = {{0}}; assert(db && short_id); @@ -958,6 +988,11 @@ int git_odb__read_header_or_object( assert(db && id && out && len_p && type_p); + *out = NULL; + + if (git_oid_iszero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot read object"); + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { *len_p = object->cached.size; *type_p = object->cached.type; @@ -965,7 +1000,6 @@ int git_odb__read_header_or_object( return 0; } - *out = NULL; error = odb_read_header_1(len_p, type_p, db, id, false); if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) @@ -1004,8 +1038,10 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, bool found = false; int error = 0; - if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0) - found = true; + if (!only_refreshed) { + if ((error = odb_read_hardcoded(&found, &raw, id)) < 0) + return error; + } for (i = 0; i < db->backends.length && !found; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); @@ -1040,8 +1076,10 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, } giterr_clear(); - if ((object = odb_object__alloc(id, &raw)) == NULL) + if ((object = odb_object__alloc(id, &raw)) == NULL) { + error = -1; goto out; + } *out = git_cache_store_raw(odb_cache(db), object); @@ -1057,6 +1095,9 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) assert(out && db && id); + if (git_oid_iszero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot read object"); + *out = git_cache_get_raw(odb_cache(db), id); if (*out != NULL) return 0; @@ -1078,11 +1119,14 @@ static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id) size_t _unused; int error; + if (git_oid_iszero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot get object type"); + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { *type_p = object->cached.type; return 0; } - + error = odb_read_header_1(&_unused, type_p, db, id, false); if (error == GIT_PASSTHROUGH) { @@ -1161,8 +1205,10 @@ static int read_prefix_1(git_odb_object **out, git_odb *db, } } - if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) + if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) { + error = -1; goto out; + } *out = git_cache_store_raw(odb_cache(db), object); @@ -1231,6 +1277,10 @@ int git_odb_write( assert(oid && db); git_odb_hash(oid, data, len, type); + + if (git_oid_iszero(oid)) + return error_null_oid(GIT_EINVALID, "cannot write object"); + if (git_odb__freshen(db, oid)) return 0; @@ -1263,13 +1313,17 @@ int git_odb_write( return error; } -static void hash_header(git_hash_ctx *ctx, git_off_t size, git_otype type) +static int hash_header(git_hash_ctx *ctx, git_off_t size, git_otype type) { char header[64]; - int hdrlen; + size_t hdrlen; + int error; - hdrlen = git_odb__format_object_header(header, sizeof(header), size, type); - git_hash_update(ctx, header, hdrlen); + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), size, type)) < 0) + return error; + + return git_hash_update(ctx, header, hdrlen); } int git_odb_open_wstream( @@ -1310,16 +1364,17 @@ int git_odb_open_wstream( ctx = git__malloc(sizeof(git_hash_ctx)); GITERR_CHECK_ALLOC(ctx); - if ((error = git_hash_ctx_init(ctx)) < 0) + if ((error = git_hash_ctx_init(ctx)) < 0 || + (error = hash_header(ctx, size, type)) < 0) goto done; - hash_header(ctx, size, type); (*stream)->hash_ctx = ctx; - (*stream)->declared_size = size; (*stream)->received_bytes = 0; done: + if (error) + git__free(ctx); return error; } @@ -1378,7 +1433,12 @@ void git_odb_stream_free(git_odb_stream *stream) stream->free(stream); } -int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) +int git_odb_open_rstream( + git_odb_stream **stream, + size_t *len, + git_otype *type, + git_odb *db, + const git_oid *oid) { size_t i, reads = 0; int error = GIT_ERROR; @@ -1391,7 +1451,7 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi if (b->readstream != NULL) { ++reads; - error = b->readstream(stream, b, oid); + error = b->readstream(stream, len, type, b, oid); } } @@ -1484,6 +1544,12 @@ int git_odb__error_notfound( return GIT_ENOTFOUND; } +static int error_null_oid(int error, const char *message) +{ + giterr_set(GITERR_ODB, "odb: %s: null OID cannot exist", message); + return error; +} + int git_odb__error_ambiguous(const char *message) { giterr_set(GITERR_ODB, "ambiguous SHA1 prefix - %s", message); diff --git a/src/odb.h b/src/odb.h index 61d687abf..b354108e7 100644 --- a/src/odb.h +++ b/src/odb.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_odb_h__ #define INCLUDE_odb_h__ +#include "common.h" + #include "git2/odb.h" #include "git2/oid.h" #include "git2/types.h" @@ -68,7 +70,7 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj); /* * Format the object header such as it would appear in the on-disk object */ -int git_odb__format_object_header(char *hdr, size_t n, git_off_t obj_len, git_otype obj_type); +int git_odb__format_object_header(size_t *out_len, char *hdr, size_t hdr_size, git_off_t obj_len, git_otype obj_type); /* * Hash an open file descriptor. * This is a performance call when the contents of a fd need to be hashed, diff --git a/src/odb_loose.c b/src/odb_loose.c index 99fdcb44f..470421e15 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include #include "git2/object.h" #include "git2/sys/odb_backend.h" @@ -15,10 +16,14 @@ #include "delta.h" #include "filebuf.h" #include "object.h" +#include "zstream.h" #include "git2/odb_backend.h" #include "git2/types.h" +/* maximum possible header length */ +#define MAX_HEADER_LEN 64 + typedef struct { /* object header data */ git_otype type; /* object type */ size_t size; /* object size */ @@ -29,6 +34,15 @@ typedef struct { git_filebuf fbuf; } loose_writestream; +typedef struct { + git_odb_stream stream; + git_map map; + char start[MAX_HEADER_LEN]; + size_t start_len; + size_t start_read; + git_zstream zstream; +} loose_readstream; + typedef struct loose_backend { git_odb_backend parent; @@ -90,313 +104,108 @@ static int object_mkdir(const git_buf *name, const loose_backend *be) GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); } -static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) +static int parse_header_packlike( + obj_hdr *out, size_t *out_len, const unsigned char *data, size_t len) { unsigned long c; - unsigned char *data = (unsigned char *)obj->ptr; size_t shift, size, used = 0; - if (git_buf_len(obj) == 0) - return 0; + if (len == 0) + goto on_error; c = data[used++]; - hdr->type = (c >> 4) & 7; + out->type = (c >> 4) & 7; size = c & 15; shift = 4; while (c & 0x80) { - if (git_buf_len(obj) <= used) - return 0; + if (len <= used) + goto on_error; + if (sizeof(size_t) * 8 <= shift) - return 0; + goto on_error; + c = data[used++]; size += (c & 0x7f) << shift; shift += 7; } - hdr->size = size; - return used; + out->size = size; + + if (out_len) + *out_len = used; + + return 0; + +on_error: + giterr_set(GITERR_OBJECT, "failed to parse loose object: invalid header"); + return -1; } -static size_t get_object_header(obj_hdr *hdr, unsigned char *data) +static int parse_header( + obj_hdr *out, + size_t *out_len, + const unsigned char *_data, + size_t data_len) { - char c, typename[10]; - size_t size, used = 0; + const char *data = (char *)_data; + size_t i, typename_len, size_idx, size_len; + int64_t size; - /* - * type name string followed by space. - */ - while ((c = data[used]) != ' ') { - typename[used++] = c; - if (used >= sizeof(typename)) - return 0; + *out_len = 0; + + /* find the object type name */ + for (i = 0, typename_len = 0; i < data_len; i++, typename_len++) { + if (data[i] == ' ') + break; } - typename[used] = 0; - if (used == 0) - return 0; - hdr->type = git_object_string2type(typename); - used++; /* consume the space */ - /* - * length follows immediately in decimal (without - * leading zeros). - */ - size = data[used++] - '0'; - if (size > 9) - return 0; - if (size) { - while ((c = data[used]) != '\0') { - size_t d = c - '0'; - if (d > 9) - break; - used++; - size = size * 10 + d; - } + if (typename_len == data_len) + goto on_error; + + out->type = git_object_stringn2type(data, typename_len); + + size_idx = typename_len + 1; + for (i = size_idx, size_len = 0; i < data_len; i++, size_len++) { + if (data[i] == '\0') + break; } - hdr->size = size; - /* - * the length must be followed by a zero byte - */ - if (data[used++] != '\0') - return 0; + if (i == data_len) + goto on_error; - return used; -} + if (git__strntol64(&size, &data[size_idx], size_len, NULL, 10) < 0 || + size < 0) + goto on_error; - - -/*********************************************************** - * - * ZLIB RELATED FUNCTIONS - * - ***********************************************************/ - -static void init_stream(z_stream *s, void *out, size_t len) -{ - memset(s, 0, sizeof(*s)); - s->next_out = out; - s->avail_out = (uInt)len; -} - -static void set_stream_input(z_stream *s, void *in, size_t len) -{ - s->next_in = in; - s->avail_in = (uInt)len; -} - -static void set_stream_output(z_stream *s, void *out, size_t len) -{ - s->next_out = out; - s->avail_out = (uInt)len; -} - - -static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) -{ - int status; - - init_stream(s, out, len); - set_stream_input(s, obj->ptr, git_buf_len(obj)); - - if ((status = inflateInit(s)) < Z_OK) - return status; - - return inflate(s, 0); -} - -static void abort_inflate(z_stream *s) -{ - inflateEnd(s); -} - -static int finish_inflate(z_stream *s) -{ - int status = Z_OK; - - while (status == Z_OK) - status = inflate(s, Z_FINISH); - - inflateEnd(s); - - if ((status != Z_STREAM_END) || (s->avail_in != 0)) { - giterr_set(GITERR_ZLIB, "failed to finish zlib inflation; stream aborted prematurely"); + if ((uint64_t)size > SIZE_MAX) { + giterr_set(GITERR_OBJECT, "object is larger than available memory"); return -1; } + out->size = size; + + if (GIT_ADD_SIZET_OVERFLOW(out_len, i, 1)) + goto on_error; + return 0; + +on_error: + giterr_set(GITERR_OBJECT, "failed to parse loose object: invalid header"); + return -1; } -static int is_zlib_compressed_data(unsigned char *data) +static int is_zlib_compressed_data(unsigned char *data, size_t data_len) { unsigned int w; + if (data_len < 2) + return 0; + w = ((unsigned int)(data[0]) << 8) + data[1]; return (data[0] & 0x8F) == 0x08 && !(w % 31); } -static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) -{ - z_stream zs; - int status = Z_OK; - - memset(&zs, 0x0, sizeof(zs)); - - zs.next_out = out; - zs.avail_out = (uInt)outlen; - - zs.next_in = in; - zs.avail_in = (uInt)inlen; - - if (inflateInit(&zs) < Z_OK) { - giterr_set(GITERR_ZLIB, "failed to inflate buffer"); - return -1; - } - - while (status == Z_OK) - status = inflate(&zs, Z_FINISH); - - inflateEnd(&zs); - - if (status != Z_STREAM_END /* || zs.avail_in != 0 */ || - zs.total_out != outlen) - { - giterr_set(GITERR_ZLIB, "failed to inflate buffer; stream aborted prematurely"); - return -1; - } - - return 0; -} - -static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) -{ - unsigned char *buf, *head = hb; - size_t tail, alloc_size; - - /* - * allocate a buffer to hold the inflated data and copy the - * initial sequence of inflated data from the tail of the - * head buffer, if any. - */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr->size, 1) || - (buf = git__malloc(alloc_size)) == NULL) { - inflateEnd(s); - return NULL; - } - tail = s->total_out - used; - if (used > 0 && tail > 0) { - if (tail > hdr->size) - tail = hdr->size; - memcpy(buf, head + used, tail); - } - used = tail; - - /* - * inflate the remainder of the object data, if any - */ - if (hdr->size < used) - inflateEnd(s); - else { - set_stream_output(s, buf + used, hdr->size - used); - if (finish_inflate(s)) { - git__free(buf); - return NULL; - } - } - - return buf; -} - -/* - * At one point, there was a loose object format that was intended to - * mimic the format used in pack-files. This was to allow easy copying - * of loose object data into packs. This format is no longer used, but - * we must still read it. - */ -static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) -{ - unsigned char *in, *buf; - obj_hdr hdr; - size_t len, used, alloclen; - - /* - * read the object header, which is an (uncompressed) - * binary encoding of the object type and size. - */ - if ((used = get_binary_object_header(&hdr, obj)) == 0 || - !git_object_typeisloose(hdr.type)) { - giterr_set(GITERR_ODB, "failed to inflate loose object"); - return -1; - } - - /* - * allocate a buffer and inflate the data into it - */ - GITERR_CHECK_ALLOC_ADD(&alloclen, hdr.size, 1); - buf = git__malloc(alloclen); - GITERR_CHECK_ALLOC(buf); - - in = ((unsigned char *)obj->ptr) + used; - len = obj->size - used; - if (inflate_buffer(in, len, buf, hdr.size) < 0) { - git__free(buf); - return -1; - } - buf[hdr.size] = '\0'; - - out->data = buf; - out->len = hdr.size; - out->type = hdr.type; - - return 0; -} - -static int inflate_disk_obj(git_rawobj *out, git_buf *obj) -{ - unsigned char head[64], *buf; - z_stream zs; - obj_hdr hdr; - size_t used; - - /* - * check for a pack-like loose object - */ - if (!is_zlib_compressed_data((unsigned char *)obj->ptr)) - return inflate_packlike_loose_disk_obj(out, obj); - - /* - * inflate the initial part of the io buffer in order - * to parse the object header (type and size). - */ - if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK || - (used = get_object_header(&hdr, head)) == 0 || - !git_object_typeisloose(hdr.type)) - { - abort_inflate(&zs); - giterr_set(GITERR_ODB, "failed to inflate disk object"); - return -1; - } - - /* - * allocate a buffer and inflate the object data into it - * (including the initial sequence in the head buffer). - */ - if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL) - return -1; - buf[hdr.size] = '\0'; - - out->data = buf; - out->len = hdr.size; - out->type = hdr.type; - - return 0; -} - - - - - - /*********************************************************** * * ODB OBJECT READING & WRITING @@ -406,6 +215,130 @@ static int inflate_disk_obj(git_rawobj *out, git_buf *obj) * ***********************************************************/ + +/* + * At one point, there was a loose object format that was intended to + * mimic the format used in pack-files. This was to allow easy copying + * of loose object data into packs. This format is no longer used, but + * we must still read it. + */ +static int read_loose_packlike(git_rawobj *out, git_buf *obj) +{ + git_buf body = GIT_BUF_INIT; + const unsigned char *obj_data; + obj_hdr hdr; + size_t obj_len, head_len, alloc_size; + int error; + + obj_data = (unsigned char *)obj->ptr; + obj_len = obj->size; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(&hdr, &head_len, obj_data, obj_len)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type) || head_len > obj_len) { + giterr_set(GITERR_ODB, "failed to inflate loose object"); + error = -1; + goto done; + } + + obj_data += head_len; + obj_len -= head_len; + + /* + * allocate a buffer and inflate the data into it + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + git_buf_init(&body, alloc_size) < 0) { + error = -1; + goto done; + } + + if ((error = git_zstream_inflatebuf(&body, obj_data, obj_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + out->data = git_buf_detach(&body); + +done: + git_buf_free(&body); + return error; +} + +static int read_loose_standard(git_rawobj *out, git_buf *obj) +{ + git_zstream zstream = GIT_ZSTREAM_INIT; + unsigned char head[MAX_HEADER_LEN], *body = NULL; + size_t decompressed, head_len, body_len, alloc_size; + obj_hdr hdr; + int error; + + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zstream, git_buf_cstr(obj), git_buf_len(obj))) < 0) + goto done; + + decompressed = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then push the + * remainder into the body buffer. + */ + if ((error = git_zstream_get_output(head, &decompressed, &zstream)) < 0 || + (error = parse_header(&hdr, &head_len, head, decompressed)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type)) { + giterr_set(GITERR_ODB, "failed to inflate disk object"); + error = -1; + goto done; + } + + /* + * allocate a buffer and inflate the object data into it + * (including the initial sequence in the head buffer). + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + (body = git__malloc(alloc_size)) == NULL) { + error = -1; + goto done; + } + + assert(decompressed >= head_len); + body_len = decompressed - head_len; + + if (body_len) + memcpy(body, head + head_len, body_len); + + decompressed = hdr.size - body_len; + if ((error = git_zstream_get_output(body + body_len, &decompressed, &zstream)) < 0) + goto done; + + if (!git_zstream_done(&zstream)) { + giterr_set(GITERR_ZLIB, "failed to finish zlib inflation: stream aborted prematurely"); + error = -1; + goto done; + } + + body[hdr.size] = '\0'; + + out->data = body; + out->len = hdr.size; + out->type = hdr.type; + +done: + if (error < 0) + git__free(body); + + git_zstream_free(&zstream); + return error; +} + static int read_loose(git_rawobj *out, git_buf *loc) { int error; @@ -420,21 +353,62 @@ static int read_loose(git_rawobj *out, git_buf *loc) out->len = 0; out->type = GIT_OBJ_BAD; - if (!(error = git_futils_readbuffer(&obj, loc->ptr))) - error = inflate_disk_obj(out, &obj); + if ((error = git_futils_readbuffer(&obj, loc->ptr)) < 0) + goto done; + if (!is_zlib_compressed_data((unsigned char *)obj.ptr, obj.size)) + error = read_loose_packlike(out, &obj); + else + error = read_loose_standard(out, &obj); + +done: git_buf_free(&obj); + return error; +} +static int read_header_loose_packlike( + git_rawobj *out, const unsigned char *data, size_t len) +{ + obj_hdr hdr; + size_t header_len; + int error; + + if ((error = parse_header_packlike(&hdr, &header_len, data, len)) < 0) + return error; + + out->len = hdr.size; + out->type = hdr.type; + + return error; +} + +static int read_header_loose_standard( + git_rawobj *out, const unsigned char *data, size_t len) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + obj_hdr hdr; + unsigned char inflated[MAX_HEADER_LEN]; + size_t header_len, inflated_len = sizeof(inflated); + int error; + + if ((error = git_zstream_init(&zs, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zs, data, len)) < 0 || + (error = git_zstream_get_output_chunk(inflated, &inflated_len, &zs)) < 0 || + (error = parse_header(&hdr, &header_len, inflated, inflated_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + +done: + git_zstream_free(&zs); return error; } static int read_header_loose(git_rawobj *out, git_buf *loc) { - int error = 0, z_return = Z_ERRNO, read_bytes; - git_file fd; - z_stream zs; - obj_hdr header_obj; - unsigned char raw_buffer[16], inflated_buffer[64]; + unsigned char obj[1024]; + int fd, obj_len, error; assert(out && loc); @@ -443,35 +417,24 @@ static int read_header_loose(git_rawobj *out, git_buf *loc) out->data = NULL; - if ((fd = git_futils_open_ro(loc->ptr)) < 0) - return fd; + if ((error = fd = git_futils_open_ro(loc->ptr)) < 0 || + (error = obj_len = p_read(fd, obj, sizeof(obj))) < 0) + goto done; - init_stream(&zs, inflated_buffer, sizeof(inflated_buffer)); + if (!is_zlib_compressed_data(obj, (size_t)obj_len)) + error = read_header_loose_packlike(out, obj, (size_t)obj_len); + else + error = read_header_loose_standard(out, obj, (size_t)obj_len); - z_return = inflateInit(&zs); - - while (z_return == Z_OK) { - if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { - set_stream_input(&zs, raw_buffer, read_bytes); - z_return = inflate(&zs, 0); - } else - z_return = Z_STREAM_END; - } - - if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR) - || get_object_header(&header_obj, inflated_buffer) == 0 - || git_object_typeisloose(header_obj.type) == 0) - { + if (!error && !git_object_typeisloose(out->type)) { giterr_set(GITERR_ZLIB, "failed to read loose object header"); error = -1; - } else { - out->len = header_obj.size; - out->type = header_obj.type; + goto done; } - finish_inflate(&zs); - p_close(fd); - +done: + if (fd >= 0) + p_close(fd); return error; } @@ -812,7 +775,7 @@ static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb return error; } -static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid) +static int loose_backend__writestream_finalize(git_odb_stream *_stream, const git_oid *oid) { loose_writestream *stream = (loose_writestream *)_stream; loose_backend *backend = (loose_backend *)_stream->backend; @@ -831,13 +794,13 @@ static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid * return error; } -static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) +static int loose_backend__writestream_write(git_odb_stream *_stream, const char *data, size_t len) { loose_writestream *stream = (loose_writestream *)_stream; return git_filebuf_write(&stream->fbuf, data, len); } -static void loose_backend__stream_free(git_odb_stream *_stream) +static void loose_backend__writestream_free(git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; @@ -856,29 +819,32 @@ static int filebuf_flags(loose_backend *backend) return flags; } -static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, git_off_t length, git_otype type) +static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backend *_backend, git_off_t length, git_otype type) { loose_backend *backend; loose_writestream *stream = NULL; - char hdr[64]; + char hdr[MAX_HEADER_LEN]; git_buf tmp_path = GIT_BUF_INIT; - int hdrlen; + size_t hdrlen; + int error; assert(_backend && length >= 0); backend = (loose_backend *)_backend; *stream_out = NULL; - hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type); + if ((error = git_odb__format_object_header(&hdrlen, + hdr, sizeof(hdr), length, type)) < 0) + return error; stream = git__calloc(1, sizeof(loose_writestream)); GITERR_CHECK_ALLOC(stream); stream->stream.backend = _backend; stream->stream.read = NULL; /* read only */ - stream->stream.write = &loose_backend__stream_write; - stream->stream.finalize_write = &loose_backend__stream_fwrite; - stream->stream.free = &loose_backend__stream_free; + stream->stream.write = &loose_backend__writestream_write; + stream->stream.finalize_write = &loose_backend__writestream_finalize; + stream->stream.free = &loose_backend__writestream_free; stream->stream.mode = GIT_STREAM_WRONLY; if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || @@ -896,18 +862,198 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ return !stream ? -1 : 0; } +static int loose_backend__readstream_read( + git_odb_stream *_stream, + char *buffer, + size_t buffer_len) +{ + loose_readstream *stream = (loose_readstream *)_stream; + size_t start_remain = stream->start_len - stream->start_read; + int total = 0, error; + + /* + * if we read more than just the header in the initial read, play + * that back for the caller. + */ + if (start_remain && buffer_len) { + size_t chunk = min(start_remain, buffer_len); + memcpy(buffer, stream->start + stream->start_read, chunk); + + buffer += chunk; + stream->start_read += chunk; + + total += chunk; + buffer_len -= chunk; + } + + if (buffer_len) { + size_t chunk = min(buffer_len, INT_MAX); + + if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) + return error; + + total += chunk; + } + + return total; +} + +static void loose_backend__readstream_free(git_odb_stream *_stream) +{ + loose_readstream *stream = (loose_readstream *)_stream; + + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git__free(stream); +} + +static int loose_backend__readstream_packlike( + obj_hdr *hdr, + loose_readstream *stream) +{ + const unsigned char *data; + size_t data_len, head_len; + int error; + + data = stream->map.data; + data_len = stream->map.len; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(hdr, &head_len, data, data_len)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + giterr_set(GITERR_ODB, "failed to inflate loose object"); + return -1; + } + + return git_zstream_set_input(&stream->zstream, + data + head_len, data_len - head_len); +} + +static int loose_backend__readstream_standard( + obj_hdr *hdr, + loose_readstream *stream) +{ + unsigned char head[MAX_HEADER_LEN]; + size_t init, head_len; + int error; + + if ((error = git_zstream_set_input(&stream->zstream, + stream->map.data, stream->map.len)) < 0) + return error; + + init = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then store + * it in the `start` field of the stream object. + */ + if ((error = git_zstream_get_output(head, &init, &stream->zstream)) < 0 || + (error = parse_header(hdr, &head_len, head, init)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + giterr_set(GITERR_ODB, "failed to inflate disk object"); + return -1; + } + + if (init > head_len) { + stream->start_len = init - head_len; + memcpy(stream->start, head + head_len, init - head_len); + } + + return 0; +} + +static int loose_backend__readstream( + git_odb_stream **stream_out, + size_t *len_out, + git_otype *type_out, + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend; + loose_readstream *stream = NULL; + git_hash_ctx *hash_ctx = NULL; + git_buf object_path = GIT_BUF_INIT; + obj_hdr hdr; + int error = 0; + + assert(stream_out && len_out && type_out && _backend && oid); + + backend = (loose_backend *)_backend; + *stream_out = NULL; + *len_out = 0; + *type_out = GIT_OBJ_BAD; + + if (locate_object(&object_path, backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + goto done; + } + + stream = git__calloc(1, sizeof(loose_readstream)); + GITERR_CHECK_ALLOC(stream); + + hash_ctx = git__malloc(sizeof(git_hash_ctx)); + GITERR_CHECK_ALLOC(hash_ctx); + + if ((error = git_hash_ctx_init(hash_ctx)) < 0 || + (error = git_futils_mmap_ro_file(&stream->map, object_path.ptr)) < 0 || + (error = git_zstream_init(&stream->zstream, GIT_ZSTREAM_INFLATE)) < 0) + goto done; + + /* check for a packlike loose object */ + if (!is_zlib_compressed_data(stream->map.data, stream->map.len)) + error = loose_backend__readstream_packlike(&hdr, stream); + else + error = loose_backend__readstream_standard(&hdr, stream); + + if (error < 0) + goto done; + + stream->stream.backend = _backend; + stream->stream.hash_ctx = hash_ctx; + stream->stream.read = &loose_backend__readstream_read; + stream->stream.free = &loose_backend__readstream_free; + + *stream_out = (git_odb_stream *)stream; + *len_out = hdr.size; + *type_out = hdr.type; + +done: + if (error < 0) { + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git_hash_ctx_cleanup(hash_ctx); + git__free(hash_ctx); + git__free(stream); + } + + git_buf_free(&object_path); + return error; +} + static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type) { - int error = 0, header_len; + int error = 0; git_buf final_path = GIT_BUF_INIT; - char header[64]; + char header[MAX_HEADER_LEN]; + size_t header_len; git_filebuf fbuf = GIT_FILEBUF_INIT; loose_backend *backend; backend = (loose_backend *)_backend; /* prepare the header for the file */ - header_len = git_odb__format_object_header(header, sizeof(header), len, type); + if ((error = git_odb__format_object_header(&header_len, + header, sizeof(header), len, type)) < 0) + goto cleanup; if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&fbuf, final_path.ptr, filebuf_flags(backend), @@ -1002,7 +1148,8 @@ int git_odb_backend_loose( backend->parent.write = &loose_backend__write; backend->parent.read_prefix = &loose_backend__read_prefix; backend->parent.read_header = &loose_backend__read_header; - backend->parent.writestream = &loose_backend__stream; + backend->parent.writestream = &loose_backend__writestream; + backend->parent.readstream = &loose_backend__readstream; backend->parent.exists = &loose_backend__exists; backend->parent.exists_prefix = &loose_backend__exists_prefix; backend->parent.foreach = &loose_backend__foreach; diff --git a/src/odb_mempack.c b/src/odb_mempack.c index d6f2fb4af..a81716ee3 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -6,8 +6,10 @@ */ #include "common.h" + #include "git2/object.h" #include "git2/sys/odb_backend.h" +#include "git2/sys/mempack.h" #include "fileops.h" #include "hash.h" #include "odb.h" diff --git a/src/odb_pack.c b/src/odb_pack.c index 51770a88e..20aff5386 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include #include "git2/repository.h" #include "git2/indexer.h" diff --git a/src/offmap.h b/src/offmap.h index f9d2483a6..0b0896b8f 100644 --- a/src/offmap.h +++ b/src/offmap.h @@ -8,6 +8,7 @@ #define INCLUDE_offmap_h__ #include "common.h" + #include "git2/types.h" #define kmalloc git__malloc diff --git a/src/oid.c b/src/oid.c index 9dc719194..0c63abb2e 100644 --- a/src/oid.c +++ b/src/oid.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "oid.h" + #include "git2/oid.h" #include "repository.h" #include "global.h" diff --git a/src/oid.h b/src/oid.h index 922a2a347..84231ffca 100644 --- a/src/oid.h +++ b/src/oid.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_oid_h__ #define INCLUDE_oid_h__ +#include "common.h" + #include "git2/oid.h" /** @@ -22,14 +24,7 @@ char *git_oid_allocfmt(const git_oid *id); GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - int i; - - for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) { - if (*sha1 != *sha2) - return *sha1 - *sha2; - } - - return 0; + return memcmp(sha1, sha2, GIT_OID_RAWSZ); } /* diff --git a/src/oidarray.c b/src/oidarray.c index 1d51a2958..e70e9dd61 100644 --- a/src/oidarray.c +++ b/src/oidarray.c @@ -5,8 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/oidarray.h" #include "oidarray.h" + +#include "git2/oidarray.h" #include "array.h" void git_oidarray_free(git_oidarray *arr) @@ -19,3 +20,15 @@ void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array) arr->count = array->size; arr->ids = array->ptr; } + +void git_oidarray__reverse(git_oidarray *arr) +{ + size_t i; + git_oid tmp; + + for (i = 0; i < arr->count / 2; i++) { + git_oid_cpy(&tmp, &arr->ids[i]); + git_oid_cpy(&arr->ids[i], &arr->ids[(arr->count-1)-i]); + git_oid_cpy(&arr->ids[(arr->count-1)-i], &tmp); + } +} diff --git a/src/oidarray.h b/src/oidarray.h index a7215ae6c..eed3a1091 100644 --- a/src/oidarray.h +++ b/src/oidarray.h @@ -8,11 +8,13 @@ #define INCLUDE_oidarray_h__ #include "common.h" + #include "git2/oidarray.h" #include "array.h" typedef git_array_t(git_oid) git_array_oid_t; +extern void git_oidarray__reverse(git_oidarray *arr); extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array); #endif diff --git a/src/oidmap.h b/src/oidmap.h index 563222494..49f129e93 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -8,6 +8,7 @@ #define INCLUDE_oidmap_h__ #include "common.h" + #include "git2/oid.h" #define kmalloc git__malloc diff --git a/src/pack-objects.c b/src/pack-objects.c index ef272e8f5..e9245143c 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -1642,7 +1642,7 @@ int insert_tree(git_packbuilder *pb, git_tree *tree) if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) return error; - if (obj->seen) + if (obj->seen || obj->uninteresting) return 0; obj->seen = 1; @@ -1666,6 +1666,10 @@ int insert_tree(git_packbuilder *pb, git_tree *tree) break; case GIT_OBJ_BLOB: + if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) + return error; + if (obj->uninteresting) + continue; name = git_tree_entry_name(entry); if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0) return error; diff --git a/src/pack-objects.h b/src/pack-objects.h index e1e0ee3c8..c9cd5777a 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -104,4 +104,4 @@ struct git_packbuilder { int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb); -#endif /* INCLUDE_pack_objects_h__ */ +#endif diff --git a/src/pack.c b/src/pack.c index f8d0dc9ac..9ed3ec1af 100644 --- a/src/pack.c +++ b/src/pack.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "odb.h" #include "pack.h" + +#include "odb.h" #include "delta.h" #include "sha1_lookup.h" #include "mwindow.h" @@ -716,8 +716,11 @@ int git_packfile_unpack( error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); git_mwindow_close(&w_curs); - if (error < 0) + if (error < 0) { + /* We have transferred ownership of the data to the cache. */ + obj->data = NULL; break; + } /* the current object becomes the new base, on which we apply the delta */ base = *obj; @@ -934,19 +937,19 @@ git_off_t get_delta_base( if (type == GIT_OBJ_OFS_DELTA) { unsigned used = 0; unsigned char c = base_info[used++]; - base_offset = c & 127; + size_t unsigned_base_offset = c & 127; while (c & 128) { if (left <= used) return GIT_EBUFS; - base_offset += 1; - if (!base_offset || MSB(base_offset, 7)) + unsigned_base_offset += 1; + if (!unsigned_base_offset || MSB(unsigned_base_offset, 7)) return 0; /* overflow */ c = base_info[used++]; - base_offset = (base_offset << 7) + (c & 127); + unsigned_base_offset = (unsigned_base_offset << 7) + (c & 127); } - base_offset = delta_obj_offset - base_offset; - if (base_offset <= 0 || base_offset >= delta_obj_offset) + if (unsigned_base_offset == 0 || (size_t)delta_obj_offset <= unsigned_base_offset) return 0; /* out of bound */ + base_offset = delta_obj_offset - unsigned_base_offset; *curpos += used; } else if (type == GIT_OBJ_REF_DELTA) { /* If we have the cooperative cache, search in it first */ @@ -1316,11 +1319,7 @@ static int pack_entry_find_offset( short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); #endif -#ifdef GIT_USE_LOOKUP - pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); -#else pos = sha1_position(index, stride, lo, hi, short_oid->id); -#endif if (pos >= 0) { /* An object matching exactly the oid was found */ diff --git a/src/pack.h b/src/pack.h index e2bf165f4..c0ca74f14 100644 --- a/src/pack.h +++ b/src/pack.h @@ -8,11 +8,12 @@ #ifndef INCLUDE_pack_h__ #define INCLUDE_pack_h__ +#include "common.h" + #include #include "git2/oid.h" -#include "common.h" #include "map.h" #include "mwindow.h" #include "odb.h" diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 000000000..6b8902c35 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "parse.h" + +int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) +{ + if (content_len) + ctx->content = content; + else + ctx->content = NULL; + + ctx->content_len = content_len; + ctx->remain = ctx->content; + ctx->remain_len = ctx->content_len; + ctx->line = ctx->remain; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num = 1; + + return 0; +} + +void git_parse_ctx_clear(git_parse_ctx *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); +} + +void git_parse_advance_line(git_parse_ctx *ctx) +{ + ctx->line += ctx->line_len; + ctx->remain_len -= ctx->line_len; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num++; +} + +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt) +{ + ctx->line += char_cnt; + ctx->remain_len -= char_cnt; + ctx->line_len -= char_cnt; +} + +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len) +{ + if (ctx->line_len < expected_len) + return -1; + + if (memcmp(ctx->line, expected, expected_len) != 0) + return -1; + + git_parse_advance_chars(ctx, expected_len); + return 0; +} + +int git_parse_advance_ws(git_parse_ctx *ctx) +{ + int ret = -1; + + while (ctx->line_len > 0 && + ctx->line[0] != '\n' && + git__isspace(ctx->line[0])) { + ctx->line++; + ctx->line_len--; + ctx->remain_len--; + ret = 0; + } + + return ret; +} + +int git_parse_advance_nl(git_parse_ctx *ctx) +{ + if (ctx->line_len != 1 || ctx->line[0] != '\n') + return -1; + + git_parse_advance_line(ctx); + return 0; +} + +int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) +{ + const char *end; + int ret; + + if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) + return -1; + + if ((ret = git__strntol64(out, ctx->line, ctx->line_len, &end, base)) < 0) + return -1; + + git_parse_advance_chars(ctx, (end - ctx->line)); + return 0; +} + +int git_parse_peek(char *out, git_parse_ctx *ctx, int flags) +{ + size_t remain = ctx->line_len; + const char *ptr = ctx->line; + + while (remain) { + char c = *ptr; + + if ((flags & GIT_PARSE_PEEK_SKIP_WHITESPACE) && + git__isspace(c)) { + remain--; + ptr++; + continue; + } + + *out = c; + return 0; + } + + return -1; +} diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 000000000..46897e306 --- /dev/null +++ b/src/parse.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_parse_h__ +#define INCLUDE_parse_h__ + +#include "common.h" + +typedef struct { + /* Original content buffer */ + const char *content; + size_t content_len; + + /* The remaining (unparsed) buffer */ + const char *remain; + size_t remain_len; + + const char *line; + size_t line_len; + size_t line_num; +} git_parse_ctx; + +int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len); +void git_parse_ctx_clear(git_parse_ctx *ctx); + +#define git_parse_err(...) \ + ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) + +#define git_parse_ctx_contains_s(ctx, str) \ + git_parse_ctx_contains(ctx, str, sizeof(str) - 1) + +GIT_INLINE(bool) git_parse_ctx_contains( + git_parse_ctx *ctx, const char *str, size_t len) +{ + return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); +} + +void git_parse_advance_line(git_parse_ctx *ctx); +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt); +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len); + +#define git_parse_advance_expected_str(ctx, str) \ + git_parse_advance_expected(ctx, str, strlen(str)) + +int git_parse_advance_ws(git_parse_ctx *ctx); +int git_parse_advance_nl(git_parse_ctx *ctx); +int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base); + +enum GIT_PARSE_PEEK_FLAGS { + GIT_PARSE_PEEK_SKIP_WHITESPACE = (1 << 0) +}; + +int git_parse_peek(char *out, git_parse_ctx *ctx, int flags); + +#endif diff --git a/src/patch.c b/src/patch.c index 9b7c9c64c..5e329518d 100644 --- a/src/patch.c +++ b/src/patch.c @@ -1,7 +1,14 @@ -#include "git2/patch.h" -#include "diff.h" +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* 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 "patch.h" +#include "git2/patch.h" +#include "diff.h" int git_patch__invoke_callbacks( git_patch *patch, diff --git a/src/patch.h b/src/patch.h index 525ae7007..156d1310e 100644 --- a/src/patch.h +++ b/src/patch.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_patch_h__ #define INCLUDE_patch_h__ +#include "common.h" + #include "git2/patch.h" #include "array.h" diff --git a/src/patch_generate.c b/src/patch_generate.c index 804fc0e09..29cda8b1c 100644 --- a/src/patch_generate.c +++ b/src/patch_generate.c @@ -4,13 +4,14 @@ * 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 "common.h" + +#include "patch_generate.h" + #include "git2/blob.h" #include "diff.h" #include "diff_generate.h" #include "diff_file.h" #include "diff_driver.h" -#include "patch_generate.h" #include "diff_xdiff.h" #include "delta.h" #include "zstream.h" @@ -138,7 +139,7 @@ static int patch_generated_alloc_from_diff( if (!(error = patch_generated_init(patch, diff, delta_index))) { patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; - GIT_REFCOUNT_INC(patch); + GIT_REFCOUNT_INC(&patch->base); } else { git__free(patch); patch = NULL; @@ -641,7 +642,7 @@ int git_patch_from_blob_and_buffer( git_patch **out, const git_blob *old_blob, const char *old_path, - const char *buf, + const void *buf, size_t buflen, const char *buf_path, const git_diff_options *opts) @@ -680,7 +681,7 @@ int git_patch_from_buffers( const void *old_buf, size_t old_len, const char *old_path, - const char *new_buf, + const void *new_buf, size_t new_len, const char *new_path, const git_diff_options *opts) diff --git a/src/patch_generate.h b/src/patch_generate.h index 2e89b5c4d..20f78cbfa 100644 --- a/src/patch_generate.h +++ b/src/patch_generate.h @@ -8,6 +8,7 @@ #define INCLUDE_patch_generate_h__ #include "common.h" + #include "diff.h" #include "diff_file.h" #include "patch.h" diff --git a/src/patch_parse.c b/src/patch_parse.c index 0a9edcd18..acdd45e82 100644 --- a/src/patch_parse.c +++ b/src/patch_parse.c @@ -4,15 +4,14 @@ * 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 "patch_parse.h" + #include "git2/patch.h" #include "patch.h" -#include "patch_parse.h" #include "diff_parse.h" #include "path.h" -#define parse_err(...) \ - ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) - typedef struct { git_patch base; @@ -34,104 +33,34 @@ typedef struct { char *old_prefix, *new_prefix; } git_patch_parsed; - -GIT_INLINE(bool) parse_ctx_contains( - git_patch_parse_ctx *ctx, const char *str, size_t len) -{ - return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); -} - -#define parse_ctx_contains_s(ctx, str) \ - parse_ctx_contains(ctx, str, sizeof(str) - 1) - -static void parse_advance_line(git_patch_parse_ctx *ctx) -{ - ctx->line += ctx->line_len; - ctx->remain_len -= ctx->line_len; - ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); - ctx->line_num++; -} - -static void parse_advance_chars(git_patch_parse_ctx *ctx, size_t char_cnt) -{ - ctx->line += char_cnt; - ctx->remain_len -= char_cnt; - ctx->line_len -= char_cnt; -} - -static int parse_advance_expected( - git_patch_parse_ctx *ctx, - const char *expected, - size_t expected_len) -{ - if (ctx->line_len < expected_len) - return -1; - - if (memcmp(ctx->line, expected, expected_len) != 0) - return -1; - - parse_advance_chars(ctx, expected_len); - return 0; -} - -#define parse_advance_expected_str(ctx, str) \ - parse_advance_expected(ctx, str, strlen(str)) - -static int parse_advance_ws(git_patch_parse_ctx *ctx) -{ - int ret = -1; - - while (ctx->line_len > 0 && - ctx->line[0] != '\n' && - git__isspace(ctx->line[0])) { - ctx->line++; - ctx->line_len--; - ctx->remain_len--; - ret = 0; - } - - return ret; -} - -static int parse_advance_nl(git_patch_parse_ctx *ctx) -{ - if (ctx->line_len != 1 || ctx->line[0] != '\n') - return -1; - - parse_advance_line(ctx); - return 0; -} - static int header_path_len(git_patch_parse_ctx *ctx) { bool inquote = 0; - bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"'); + bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); size_t len; - for (len = quoted; len < ctx->line_len; len++) { - if (!quoted && git__isspace(ctx->line[len])) + for (len = quoted; len < ctx->parse_ctx.line_len; len++) { + if (!quoted && git__isspace(ctx->parse_ctx.line[len])) break; - else if (quoted && !inquote && ctx->line[len] == '"') { + else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { len++; break; } - inquote = (!inquote && ctx->line[len] == '\\'); + inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); } return len; } -static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx) +static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t path_len) { - int path_len, error = 0; + int error; - path_len = header_path_len(ctx); - - if ((error = git_buf_put(path, ctx->line, path_len)) < 0) + if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0) goto done; - parse_advance_chars(ctx, path_len); + git_parse_advance_chars(&ctx->parse_ctx, path_len); git_buf_rtrim(path); @@ -150,7 +79,7 @@ done: static int parse_header_path(char **out, git_patch_parse_ctx *ctx) { git_buf path = GIT_BUF_INIT; - int error = parse_header_path_buf(&path, ctx); + int error = parse_header_path_buf(&path, ctx, header_path_len(ctx)); *out = git_buf_detach(&path); @@ -160,35 +89,48 @@ static int parse_header_path(char **out, git_patch_parse_ctx *ctx) static int parse_header_git_oldpath( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - return parse_header_path(&patch->old_path, ctx); + git_buf old_path = GIT_BUF_INIT; + int error; + + if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + goto out; + + patch->old_path = git_buf_detach(&old_path); + +out: + git_buf_free(&old_path); + return error; } static int parse_header_git_newpath( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - return parse_header_path(&patch->new_path, ctx); + git_buf new_path = GIT_BUF_INIT; + int error; + + if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + goto out; + + patch->new_path = git_buf_detach(&new_path); + +out: + git_buf_free(&new_path); + return error; } static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) { - const char *end; - int32_t m; - int ret; + int64_t m; - if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) - return parse_err("invalid file mode at line %"PRIuZ, ctx->line_num); - - if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0) - return ret; + if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) + return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); if (m > UINT16_MAX) return -1; *mode = (uint16_t)m; - parse_advance_chars(ctx, (end - ctx->line)); - - return ret; + return 0; } static int parse_header_oid( @@ -198,17 +140,17 @@ static int parse_header_oid( { size_t len; - for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) { - if (!git__isxdigit(ctx->line[len])) + for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { + if (!git__isxdigit(ctx->parse_ctx.line[len])) break; } if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || - git_oid_fromstrn(oid, ctx->line, len) < 0) - return parse_err("invalid hex formatted object id at line %"PRIuZ, - ctx->line_num); + git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) + return git_parse_err("invalid hex formatted object id at line %"PRIuZ, + ctx->parse_ctx.line_num); - parse_advance_chars(ctx, len); + git_parse_advance_chars(&ctx->parse_ctx, len); *oid_len = (uint16_t)len; @@ -218,17 +160,19 @@ static int parse_header_oid( static int parse_header_git_index( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { + char c; + if (parse_header_oid(&patch->base.delta->old_file.id, &patch->base.delta->old_file.id_abbrev, ctx) < 0 || - parse_advance_expected_str(ctx, "..") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || parse_header_oid(&patch->base.delta->new_file.id, &patch->base.delta->new_file.id_abbrev, ctx) < 0) return -1; - if (ctx->line_len > 0 && ctx->line[0] == ' ') { + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { uint16_t mode; - parse_advance_chars(ctx, 1); + git_parse_advance_chars(&ctx->parse_ctx, 1); if (parse_header_mode(&mode, ctx) < 0) return -1; @@ -287,7 +231,7 @@ static int parse_header_rename( { git_buf path = GIT_BUF_INIT; - if (parse_header_path_buf(&path, ctx) < 0) + if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) return -1; /* Note: the `rename from` and `rename to` lines include the literal @@ -327,19 +271,15 @@ static int parse_header_copyto( static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) { - int32_t val; - const char *end; + int64_t val; - if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) || - git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0) + if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) return -1; - parse_advance_chars(ctx, (end - ctx->line)); - - if (parse_advance_expected_str(ctx, "%") < 0) + if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) return -1; - if (val > 100) + if (val < 0 || val > 100) return -1; *out = val; @@ -350,8 +290,8 @@ static int parse_header_similarity( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) - return parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->line_num); + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); return 0; } @@ -362,39 +302,98 @@ static int parse_header_dissimilarity( uint16_t dissimilarity; if (parse_header_percent(&dissimilarity, ctx) < 0) - return parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->line_num); + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); patch->base.delta->similarity = 100 - dissimilarity; return 0; } +static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_path(&patch->header_old_path, ctx) < 0) + return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || + parse_header_path(&patch->header_new_path, ctx) < 0) + return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + /* + * We cannot expect to be able to always parse paths correctly at this + * point. Due to the possibility of unquoted names, whitespaces in + * filenames and custom prefixes we have to allow that, though, and just + * proceeed here. We then hope for the "---" and "+++" lines to fix that + * for us. + */ + if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1)) { + git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); + + git__free(patch->header_old_path); + patch->header_old_path = NULL; + git__free(patch->header_new_path); + patch->header_new_path = NULL; + } + + return 0; +} + +typedef enum { + STATE_START, + + STATE_DIFF, + STATE_FILEMODE, + STATE_MODE, + STATE_INDEX, + STATE_PATH, + + STATE_SIMILARITY, + STATE_RENAME, + STATE_COPY, + + STATE_END, +} parse_header_state; + typedef struct { const char *str; + parse_header_state expected_state; + parse_header_state next_state; int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); -} header_git_op; +} parse_header_transition; -static const header_git_op header_git_ops[] = { - { "diff --git ", NULL }, - { "@@ -", NULL }, - { "GIT binary patch", NULL }, - { "Binary files ", NULL }, - { "--- ", parse_header_git_oldpath }, - { "+++ ", parse_header_git_newpath }, - { "index ", parse_header_git_index }, - { "old mode ", parse_header_git_oldmode }, - { "new mode ", parse_header_git_newmode }, - { "deleted file mode ", parse_header_git_deletedfilemode }, - { "new file mode ", parse_header_git_newfilemode }, - { "rename from ", parse_header_renamefrom }, - { "rename to ", parse_header_renameto }, - { "rename old ", parse_header_renamefrom }, - { "rename new ", parse_header_renameto }, - { "copy from ", parse_header_copyfrom }, - { "copy to ", parse_header_copyto }, - { "similarity index ", parse_header_similarity }, - { "dissimilarity index ", parse_header_dissimilarity }, +static const parse_header_transition transitions[] = { + /* Start */ + { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, + + { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, + { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, + { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, + { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, + + { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, + { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, + { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, + + { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, + { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, + { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, + { "Binary files " , STATE_INDEX, STATE_END, NULL }, + + { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, + { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, + { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, + { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, + + /* Next patch */ + { "diff --git " , STATE_END, 0, NULL }, + { "@@ -" , STATE_END, 0, NULL }, + { "-- " , STATE_END, 0, NULL }, }; static int parse_header_git( @@ -403,65 +402,58 @@ static int parse_header_git( { size_t i; int error = 0; - - /* Parse the diff --git line */ - if (parse_advance_expected_str(ctx, "diff --git ") < 0) - return parse_err("corrupt git diff header at line %"PRIuZ, ctx->line_num); - - if (parse_header_path(&patch->header_old_path, ctx) < 0) - return parse_err("corrupt old path in git diff header at line %"PRIuZ, - ctx->line_num); - - if (parse_advance_ws(ctx) < 0 || - parse_header_path(&patch->header_new_path, ctx) < 0) - return parse_err("corrupt new path in git diff header at line %"PRIuZ, - ctx->line_num); + parse_header_state state = STATE_START; /* Parse remaining header lines */ - for (parse_advance_line(ctx); - ctx->remain_len > 0; - parse_advance_line(ctx)) { - + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { bool found = false; - if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') break; - for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) { - const header_git_op *op = &header_git_ops[i]; - size_t op_len = strlen(op->str); + for (i = 0; i < ARRAY_SIZE(transitions); i++) { + const parse_header_transition *transition = &transitions[i]; + size_t op_len = strlen(transition->str); - if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0) + if (transition->expected_state != state || + git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) continue; + state = transition->next_state; + /* Do not advance if this is the patch separator */ - if (op->fn == NULL) + if (transition->fn == NULL) goto done; - parse_advance_chars(ctx, op_len); + git_parse_advance_chars(&ctx->parse_ctx, op_len); - if ((error = op->fn(patch, ctx)) < 0) + if ((error = transition->fn(patch, ctx)) < 0) goto done; - parse_advance_ws(ctx); + git_parse_advance_ws(&ctx->parse_ctx); - if (parse_advance_expected_str(ctx, "\n") < 0 || - ctx->line_len > 0) { - error = parse_err("trailing data at line %"PRIuZ, ctx->line_num); + if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || + ctx->parse_ctx.line_len > 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } found = true; break; } - + if (!found) { - error = parse_err("invalid patch header at line %"PRIuZ, - ctx->line_num); + error = git_parse_err("invalid patch header at line %"PRIuZ, + ctx->parse_ctx.line_num); goto done; } } + if (state != STATE_END) { + error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + done: return error; } @@ -471,17 +463,17 @@ static int parse_number(git_off_t *out, git_patch_parse_ctx *ctx) const char *end; int64_t num; - if (!git__isdigit(ctx->line[0])) + if (!git__isdigit(ctx->parse_ctx.line[0])) return -1; - if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0) + if (git__strntol64(&num, ctx->parse_ctx.line, ctx->parse_ctx.line_len, &end, 10) < 0) return -1; if (num < 0) return -1; *out = num; - parse_advance_chars(ctx, (end - ctx->line)); + git_parse_advance_chars(&ctx->parse_ctx, (end - ctx->parse_ctx.line)); return 0; } @@ -490,7 +482,7 @@ static int parse_int(int *out, git_patch_parse_ctx *ctx) { git_off_t num; - if (parse_number(&num, ctx) < 0 || !git__is_int(num)) + if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) return -1; *out = (int)num; @@ -501,43 +493,44 @@ static int parse_hunk_header( git_patch_hunk *hunk, git_patch_parse_ctx *ctx) { - const char *header_start = ctx->line; + const char *header_start = ctx->parse_ctx.line; + char c; hunk->hunk.old_lines = 1; hunk->hunk.new_lines = 1; - if (parse_advance_expected_str(ctx, "@@ -") < 0 || + if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || parse_int(&hunk->hunk.old_start, ctx) < 0) goto fail; - if (ctx->line_len > 0 && ctx->line[0] == ',') { - if (parse_advance_expected_str(ctx, ",") < 0 || + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || parse_int(&hunk->hunk.old_lines, ctx) < 0) goto fail; } - if (parse_advance_expected_str(ctx, " +") < 0 || + if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || parse_int(&hunk->hunk.new_start, ctx) < 0) goto fail; - if (ctx->line_len > 0 && ctx->line[0] == ',') { - if (parse_advance_expected_str(ctx, ",") < 0 || + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || parse_int(&hunk->hunk.new_lines, ctx) < 0) goto fail; } - if (parse_advance_expected_str(ctx, " @@") < 0) + if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) goto fail; - parse_advance_line(ctx); + git_parse_advance_line(&ctx->parse_ctx); if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) goto fail; - hunk->hunk.header_len = ctx->line - header_start; + hunk->hunk.header_len = ctx->parse_ctx.line - header_start; if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) - return parse_err("oversized patch hunk header at line %"PRIuZ, - ctx->line_num); + return git_parse_err("oversized patch hunk header at line %"PRIuZ, + ctx->parse_ctx.line_num); memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); hunk->hunk.header[hunk->hunk.header_len] = '\0'; @@ -546,7 +539,7 @@ static int parse_hunk_header( fail: giterr_set(GITERR_PATCH, "invalid patch hunk header at line %"PRIuZ, - ctx->line_num); + ctx->parse_ctx.line_num); return -1; } @@ -562,23 +555,27 @@ static int parse_hunk_body( int newlines = hunk->hunk.new_lines; for (; - ctx->remain_len > 1 && + ctx->parse_ctx.remain_len > 1 && (oldlines || newlines) && - (ctx->remain_len <= 4 || memcmp(ctx->line, "@@ -", 4) != 0); - parse_advance_line(ctx)) { + !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); + git_parse_advance_line(&ctx->parse_ctx)) { + char c; int origin; int prefix = 1; - if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') { - error = parse_err("invalid patch instruction at line %"PRIuZ, - ctx->line_num); + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { + error = git_parse_err("invalid patch instruction at line %"PRIuZ, + ctx->parse_ctx.line_num); goto done; } - switch (ctx->line[0]) { + git_parse_peek(&c, &ctx->parse_ctx, 0); + + switch (c) { case '\n': prefix = 0; + /* fall through */ case ' ': origin = GIT_DIFF_LINE_CONTEXT; @@ -597,7 +594,7 @@ static int parse_hunk_body( break; default: - error = parse_err("invalid patch hunk at line %"PRIuZ, ctx->line_num); + error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } @@ -606,16 +603,16 @@ static int parse_hunk_body( memset(line, 0x0, sizeof(git_diff_line)); - line->content = ctx->line + prefix; - line->content_len = ctx->line_len - prefix; - line->content_offset = ctx->content_len - ctx->remain_len; + line->content = ctx->parse_ctx.line + prefix; + line->content_len = ctx->parse_ctx.line_len - prefix; + line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; line->origin = origin; hunk->line_count++; } if (oldlines || newlines) { - error = parse_err( + error = git_parse_err( "invalid patch hunk, expected %d old lines and %d new lines", hunk->hunk.old_lines, hunk->hunk.new_lines); goto done; @@ -626,19 +623,19 @@ static int parse_hunk_body( * localized. Because `diff` optimizes for the case where you * want to apply the patch by hand. */ - if (parse_ctx_contains_s(ctx, "\\ ") && + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && git_array_size(patch->base.lines) > 0) { line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); if (line->content_len < 1) { - error = parse_err("cannot trim trailing newline of empty line"); + error = git_parse_err("cannot trim trailing newline of empty line"); goto done; } line->content_len--; - parse_advance_line(ctx); + git_parse_advance_line(&ctx->parse_ctx); } done: @@ -651,18 +648,15 @@ static int parse_patch_header( { int error = 0; - for (ctx->line = ctx->remain; - ctx->remain_len > 0; - parse_advance_line(ctx)) { - + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { /* This line is too short to be a patch header. */ - if (ctx->line_len < 6) + if (ctx->parse_ctx.line_len < 6) continue; /* This might be a hunk header without a patch header, provide a * sensible error message. */ - if (parse_ctx_contains_s(ctx, "@@ -")) { - size_t line_num = ctx->line_num; + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { + size_t line_num = ctx->parse_ctx.line_num; git_patch_hunk hunk; /* If this cannot be parsed as a hunk header, it's just leading @@ -673,17 +667,17 @@ static int parse_patch_header( continue; } - error = parse_err("invalid hunk header outside patch at line %"PRIuZ, + error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, line_num); goto done; } /* This buffer is too short to contain a patch. */ - if (ctx->remain_len < ctx->line_len + 6) + if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) break; /* A proper git patch */ - if (parse_ctx_contains_s(ctx, "diff --git ")) { + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { error = parse_header_git(patch, ctx); goto done; } @@ -708,27 +702,30 @@ static int parse_patch_binary_side( git_off_t len; int error = 0; - if (parse_ctx_contains_s(ctx, "literal ")) { + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { type = GIT_DIFF_BINARY_LITERAL; - parse_advance_chars(ctx, 8); - } else if (parse_ctx_contains_s(ctx, "delta ")) { + git_parse_advance_chars(&ctx->parse_ctx, 8); + } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { type = GIT_DIFF_BINARY_DELTA; - parse_advance_chars(ctx, 6); + git_parse_advance_chars(&ctx->parse_ctx, 6); } else { - error = parse_err( - "unknown binary delta type at line %"PRIuZ, ctx->line_num); + error = git_parse_err( + "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } - if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) { - error = parse_err("invalid binary size at line %"PRIuZ, ctx->line_num); + if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { + error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } - while (ctx->line_len) { - char c = ctx->line[0]; + while (ctx->parse_ctx.line_len) { + char c; size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; + git_parse_peek(&c, &ctx->parse_ctx, 0); + if (c == '\n') break; else if (c >= 'A' && c <= 'Z') @@ -737,32 +734,32 @@ static int parse_patch_binary_side( decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; if (!decoded_len) { - error = parse_err("invalid binary length at line %"PRIuZ, ctx->line_num); + error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } - parse_advance_chars(ctx, 1); + git_parse_advance_chars(&ctx->parse_ctx, 1); encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; - if (encoded_len > ctx->line_len - 1) { - error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num); + if (encoded_len > ctx->parse_ctx.line_len - 1) { + error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } if ((error = git_buf_decode_base85( - &decoded, ctx->line, encoded_len, decoded_len)) < 0) + &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) goto done; if (decoded.size - decoded_orig != decoded_len) { - error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num); + error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } - parse_advance_chars(ctx, encoded_len); + git_parse_advance_chars(&ctx->parse_ctx, encoded_len); - if (parse_advance_nl(ctx) < 0) { - error = parse_err("trailing data at line %"PRIuZ, ctx->line_num); + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } } @@ -784,27 +781,27 @@ static int parse_patch_binary( { int error; - if (parse_advance_expected_str(ctx, "GIT binary patch") < 0 || - parse_advance_nl(ctx) < 0) - return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num); + if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); /* parse old->new binary diff */ if ((error = parse_patch_binary_side( &patch->base.binary.new_file, ctx)) < 0) return error; - if (parse_advance_nl(ctx) < 0) - return parse_err("corrupt git binary separator at line %"PRIuZ, - ctx->line_num); + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary separator at line %"PRIuZ, + ctx->parse_ctx.line_num); /* parse new->old binary diff */ if ((error = parse_patch_binary_side( &patch->base.binary.old_file, ctx)) < 0) return error; - if (parse_advance_nl(ctx) < 0) - return parse_err("corrupt git binary patch separator at line %"PRIuZ, - ctx->line_num); + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, + ctx->parse_ctx.line_num); patch->base.binary.contains_data = 1; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; @@ -815,13 +812,13 @@ static int parse_patch_binary_nodata( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - if (parse_advance_expected_str(ctx, "Binary files ") < 0 || - parse_advance_expected_str(ctx, patch->header_old_path) < 0 || - parse_advance_expected_str(ctx, " and ") < 0 || - parse_advance_expected_str(ctx, patch->header_new_path) < 0 || - parse_advance_expected_str(ctx, " differ") < 0 || - parse_advance_nl(ctx) < 0) - return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num); + if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_old_path) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_new_path) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); patch->base.binary.contains_data = 0; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; @@ -835,7 +832,7 @@ static int parse_patch_hunks( git_patch_hunk *hunk; int error = 0; - while (parse_ctx_contains_s(ctx, "@@ -")) { + while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { hunk = git_array_alloc(patch->base.hunks); GITERR_CHECK_ALLOC(hunk); @@ -858,9 +855,9 @@ done: static int parse_patch_body( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - if (parse_ctx_contains_s(ctx, "GIT binary patch")) + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) return parse_patch_binary(patch, ctx); - else if (parse_ctx_contains_s(ctx, "Binary files ")) + else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) return parse_patch_binary_nodata(patch, ctx); else return parse_patch_hunks(patch, ctx); @@ -876,10 +873,10 @@ int check_header_names( return 0; if (two_null && strcmp(two, "/dev/null") != 0) - return parse_err("expected %s path of '/dev/null'", old_or_new); + return git_parse_err("expected %s path of '/dev/null'", old_or_new); else if (!two_null && strcmp(one, two) != 0) - return parse_err("mismatched %s path names", old_or_new); + return git_parse_err("mismatched %s path names", old_or_new); return 0; } @@ -912,7 +909,7 @@ static int check_prefix( } if (remain_len || !*path) - return parse_err( + return git_parse_err( "header filename does not contain %"PRIuZ" path components", prefix_len); @@ -931,10 +928,10 @@ static int check_filenames(git_patch_parsed *patch) bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); if (patch->old_path && !patch->new_path) - return parse_err("missing new path"); + return git_parse_err("missing new path"); if (!patch->old_path && patch->new_path) - return parse_err("missing old path"); + return git_parse_err("missing old path"); /* Ensure (non-renamed) paths match */ if (check_header_names( @@ -967,7 +964,7 @@ static int check_filenames(git_patch_parsed *patch) if (!patch->base.delta->old_file.path && !patch->base.delta->new_file.path) - return parse_err("git diff header lacks old / new paths"); + return git_parse_err("git diff header lacks old / new paths"); return 0; } @@ -988,7 +985,7 @@ static int check_patch(git_patch_parsed *patch) !(delta->flags & GIT_DIFF_FLAG_BINARY) && delta->new_file.mode == delta->old_file.mode && git_array_size(patch->base.hunks) == 0) - return parse_err("patch with no hunks"); + return git_parse_err("patch with no hunks"); if (delta->status == GIT_DELTA_ADDED) { memset(&delta->old_file.id, 0x0, sizeof(git_oid)); @@ -1014,19 +1011,11 @@ git_patch_parse_ctx *git_patch_parse_ctx_init( if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) return NULL; - if (content_len) { - if ((ctx->content = git__malloc(content_len)) == NULL) { - git__free(ctx); - return NULL; - } - - memcpy((char *)ctx->content, content, content_len); + if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { + git__free(ctx); + return NULL; } - ctx->content_len = content_len; - ctx->remain = ctx->content; - ctx->remain_len = ctx->content_len; - if (opts) memcpy(&ctx->opts, opts, sizeof(git_patch_options)); else @@ -1041,7 +1030,7 @@ static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) if (!ctx) return; - git__free((char *)ctx->content); + git_parse_ctx_clear(&ctx->parse_ctx); git__free(ctx); } @@ -1116,21 +1105,21 @@ int git_patch_parse( patch->base.delta->status = GIT_DELTA_MODIFIED; patch->base.delta->nfiles = 2; - start = ctx->remain_len; + start = ctx->parse_ctx.remain_len; if ((error = parse_patch_header(patch, ctx)) < 0 || (error = parse_patch_body(patch, ctx)) < 0 || (error = check_patch(patch)) < 0) goto done; - used = start - ctx->remain_len; - ctx->remain += used; + used = start - ctx->parse_ctx.remain_len; + ctx->parse_ctx.remain += used; patch->base.diff_opts.old_prefix = patch->old_prefix; patch->base.diff_opts.new_prefix = patch->new_prefix; patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; - GIT_REFCOUNT_INC(patch); + GIT_REFCOUNT_INC(&patch->base); *out = &patch->base; done: diff --git a/src/patch_parse.h b/src/patch_parse.h index 99eb4587b..140629da8 100644 --- a/src/patch_parse.h +++ b/src/patch_parse.h @@ -7,22 +7,17 @@ #ifndef INCLUDE_patch_parse_h__ #define INCLUDE_patch_parse_h__ +#include "common.h" + +#include "parse.h" +#include "patch.h" + typedef struct { git_refcount rc; - /* Original content buffer */ - const char *content; - size_t content_len; - git_patch_options opts; - /* The remaining (unparsed) buffer */ - const char *remain; - size_t remain_len; - - const char *line; - size_t line_len; - size_t line_num; + git_parse_ctx parse_ctx; } git_patch_parse_ctx; extern git_patch_parse_ctx *git_patch_parse_ctx_init( diff --git a/src/path.c b/src/path.c index 5fc7a055b..ea0a3c3f6 100644 --- a/src/path.c +++ b/src/path.c @@ -4,8 +4,9 @@ * 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 "common.h" + #include "path.h" + #include "posix.h" #include "repository.h" #ifdef GIT_WIN32 diff --git a/src/path.h b/src/path.h index fb45a6534..aa24bcd2f 100644 --- a/src/path.h +++ b/src/path.h @@ -8,6 +8,7 @@ #define INCLUDE_path_h__ #include "common.h" + #include "posix.h" #include "buffer.h" #include "vector.h" @@ -104,6 +105,12 @@ GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name) (name[1] == L'.' && name[2] == L'\0'))); } +#define git_path_is_absolute(p) \ + (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) + +#define git_path_is_dirsep(p) \ + ((p) == '/' || (p) == '\\') + /** * Convert backslashes in path to forward slashes. */ @@ -118,6 +125,13 @@ GIT_INLINE(void) git_path_mkposix(char *path) } #else # define git_path_mkposix(p) /* blank */ + +#define git_path_is_absolute(p) \ + ((p)[0] == '/') + +#define git_path_is_dirsep(p) \ + ((p) == '/') + #endif /** diff --git a/src/pathspec.c b/src/pathspec.c index 00dba4f6b..998b6fb36 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -5,9 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "pathspec.h" + #include "git2/pathspec.h" #include "git2/diff.h" -#include "pathspec.h" #include "buf_text.h" #include "attr_file.h" #include "iterator.h" diff --git a/src/pathspec.h b/src/pathspec.h index 40cd21c3f..c4d1a83d3 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -8,7 +8,8 @@ #define INCLUDE_pathspec_h__ #include "common.h" -#include + +#include "git2/pathspec.h" #include "buffer.h" #include "vector.h" #include "pool.h" diff --git a/src/pool.c b/src/pool.c index b4fc50fca..c0efe9c9d 100644 --- a/src/pool.c +++ b/src/pool.c @@ -1,4 +1,12 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "pool.h" + #include "posix.h" #ifndef GIT_WIN32 #include diff --git a/src/pool.h b/src/pool.h index f61f16944..92ddf994a 100644 --- a/src/pool.h +++ b/src/pool.h @@ -8,6 +8,7 @@ #define INCLUDE_pool_h__ #include "common.h" + #include "vector.h" typedef struct git_pool_page git_pool_page; diff --git a/src/posix.c b/src/posix.c index 94deb6ab0..6e7985ece 100644 --- a/src/posix.c +++ b/src/posix.c @@ -4,8 +4,9 @@ * 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 "common.h" + #include "posix.h" + #include "path.h" #include #include @@ -40,7 +41,7 @@ int p_getaddrinfo( if (ainfo->ai_servent) ainfo->ai_port = ainfo->ai_servent->s_port; else - ainfo->ai_port = atol(port); + ainfo->ai_port = htons(atol(port)); memcpy(&ainfo->ai_addr_in.sin_addr, ainfo->ai_hostent->h_addr_list[0], diff --git a/src/posix.h b/src/posix.h index d26371bca..2934f2479 100644 --- a/src/posix.h +++ b/src/posix.h @@ -8,6 +8,7 @@ #define INCLUDE_posix_h__ #include "common.h" + #include #include #include "fnmatch.h" @@ -59,6 +60,9 @@ #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif /* access() mode parameter #defines */ #ifndef F_OK diff --git a/src/pqueue.c b/src/pqueue.c index 9341d1af3..3820e999c 100644 --- a/src/pqueue.c +++ b/src/pqueue.c @@ -6,6 +6,7 @@ */ #include "pqueue.h" + #include "util.h" #define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) diff --git a/src/pqueue.h b/src/pqueue.h index 76b14919e..c0a6cd49e 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_pqueue_h__ #define INCLUDE_pqueue_h__ +#include "common.h" + #include "vector.h" typedef git_vector git_pqueue; diff --git a/src/proxy.c b/src/proxy.c index f53ac1151..9bc2c7fb1 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "proxy.h" + #include "git2/proxy.h" int git_proxy_init_options(git_proxy_options *opts, unsigned int version) @@ -30,3 +31,9 @@ int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) return 0; } + +void git_proxy_options_clear(git_proxy_options *opts) +{ + git__free((char *) opts->url); + opts->url = NULL; +} diff --git a/src/proxy.h b/src/proxy.h index bf9382737..f8b5c4b50 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -7,8 +7,11 @@ #ifndef INCLUDE_proxy_h__ #define INCLUDE_proxy_h__ +#include "common.h" + #include "git2/proxy.h" extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); +extern void git_proxy_options_clear(git_proxy_options *opts); -#endif \ No newline at end of file +#endif diff --git a/src/push.c b/src/push.c index 09c234034..85b683e62 100644 --- a/src/push.c +++ b/src/push.c @@ -5,14 +5,14 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "push.h" + #include "git2.h" -#include "common.h" #include "pack.h" #include "pack-objects.h" #include "remote.h" #include "vector.h" -#include "push.h" #include "tree.h" static int push_spec_rref_cmp(const void *a, const void *b) @@ -178,6 +178,9 @@ int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) if (!fetch_spec) continue; + /* Clear the buffer which can be dirty from previous iteration */ + git_buf_clear(&remote_ref_name); + if ((error = git_refspec_transform(&remote_ref_name, fetch_spec, status->ref)) < 0) goto on_error; @@ -260,12 +263,11 @@ static int enqueue_tag(git_object **out, git_push *push, git_oid *id) return error; } -static int revwalk(git_vector *commits, git_push *push) +static int queue_objects(git_push *push) { git_remote_head *head; push_spec *spec; git_revwalk *rw; - git_oid oid; unsigned int i; int error = -1; @@ -350,176 +352,10 @@ static int revwalk(git_vector *commits, git_push *push) git_revwalk_hide(rw, &head->oid); } - while ((error = git_revwalk_next(&oid, rw)) == 0) { - git_oid *o = git__malloc(GIT_OID_RAWSZ); - if (!o) { - error = -1; - goto on_error; - } - git_oid_cpy(o, &oid); - if ((error = git_vector_insert(commits, o)) < 0) - goto on_error; - } + error = git_packbuilder_insert_walk(push->pb, rw); on_error: git_revwalk_free(rw); - return error == GIT_ITEROVER ? 0 : error; -} - -static int enqueue_object( - const git_tree_entry *entry, - git_packbuilder *pb) -{ - switch (git_tree_entry_type(entry)) { - case GIT_OBJ_COMMIT: - return 0; - case GIT_OBJ_TREE: - return git_packbuilder_insert_tree(pb, entry->oid); - default: - return git_packbuilder_insert(pb, entry->oid, entry->filename); - } -} - -static int queue_differences( - git_tree *base, - git_tree *delta, - git_packbuilder *pb) -{ - git_tree *b_child = NULL, *d_child = NULL; - size_t b_length = git_tree_entrycount(base); - size_t d_length = git_tree_entrycount(delta); - size_t i = 0, j = 0; - int error; - - while (i < b_length && j < d_length) { - const git_tree_entry *b_entry = git_tree_entry_byindex(base, i); - const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j); - int cmp = 0; - - if (!git_oid__cmp(b_entry->oid, d_entry->oid)) - goto loop; - - cmp = strcmp(b_entry->filename, d_entry->filename); - - /* If the entries are both trees and they have the same name but are - * different, then we'll recurse after adding the right-hand entry */ - if (!cmp && - git_tree_entry__is_tree(b_entry) && - git_tree_entry__is_tree(d_entry)) { - /* Add the right-hand entry */ - if ((error = git_packbuilder_insert(pb, d_entry->oid, - d_entry->filename)) < 0) - goto on_error; - - /* Acquire the subtrees and recurse */ - if ((error = git_tree_lookup(&b_child, - git_tree_owner(base), b_entry->oid)) < 0 || - (error = git_tree_lookup(&d_child, - git_tree_owner(delta), d_entry->oid)) < 0 || - (error = queue_differences(b_child, d_child, pb)) < 0) - goto on_error; - - git_tree_free(b_child); b_child = NULL; - git_tree_free(d_child); d_child = NULL; - } - /* If the object is new or different in the right-hand tree, - * then enumerate it */ - else if (cmp >= 0 && - (error = enqueue_object(d_entry, pb)) < 0) - goto on_error; - - loop: - if (cmp <= 0) i++; - if (cmp >= 0) j++; - } - - /* Drain the right-hand tree of entries */ - for (; j < d_length; j++) - if ((error = enqueue_object(git_tree_entry_byindex(delta, j), pb)) < 0) - goto on_error; - - error = 0; - -on_error: - if (b_child) - git_tree_free(b_child); - - if (d_child) - git_tree_free(d_child); - - return error; -} - -static int queue_objects(git_push *push) -{ - git_vector commits = GIT_VECTOR_INIT; - git_oid *oid; - size_t i; - unsigned j; - int error; - - if ((error = revwalk(&commits, push)) < 0) - goto on_error; - - git_vector_foreach(&commits, i, oid) { - git_commit *parent = NULL, *commit; - git_tree *tree = NULL, *ptree = NULL; - size_t parentcount; - - if ((error = git_commit_lookup(&commit, push->repo, oid)) < 0) - goto on_error; - - /* Insert the commit */ - if ((error = git_packbuilder_insert(push->pb, oid, NULL)) < 0) - goto loop_error; - - parentcount = git_commit_parentcount(commit); - - if (!parentcount) { - if ((error = git_packbuilder_insert_tree(push->pb, - git_commit_tree_id(commit))) < 0) - goto loop_error; - } else { - if ((error = git_tree_lookup(&tree, push->repo, - git_commit_tree_id(commit))) < 0 || - (error = git_packbuilder_insert(push->pb, - git_commit_tree_id(commit), NULL)) < 0) - goto loop_error; - - /* For each parent, add the items which are different */ - for (j = 0; j < parentcount; j++) { - if ((error = git_commit_parent(&parent, commit, j)) < 0 || - (error = git_commit_tree(&ptree, parent)) < 0 || - (error = queue_differences(ptree, tree, push->pb)) < 0) - goto loop_error; - - git_tree_free(ptree); ptree = NULL; - git_commit_free(parent); parent = NULL; - } - } - - error = 0; - - loop_error: - if (tree) - git_tree_free(tree); - - if (ptree) - git_tree_free(ptree); - - if (parent) - git_commit_free(parent); - - git_commit_free(commit); - - if (error < 0) - goto on_error; - } - - error = 0; - -on_error: - git_vector_free_deep(&commits); return error; } diff --git a/src/push.h b/src/push.h index e32ad2f4d..31ac43609 100644 --- a/src/push.h +++ b/src/push.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_push_h__ #define INCLUDE_push_h__ +#include "common.h" + #include "git2.h" #include "refspec.h" diff --git a/src/rebase.c b/src/rebase.c index f528031b3..3be751254 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "buffer.h" #include "repository.h" #include "posix.h" diff --git a/src/refdb.c b/src/refdb.c index 1ee0efb31..c162a153f 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -5,8 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "posix.h" +#include "refdb.h" #include "git2/object.h" #include "git2/refs.h" @@ -14,9 +13,9 @@ #include "git2/sys/refdb_backend.h" #include "hash.h" -#include "refdb.h" #include "refs.h" #include "reflog.h" +#include "posix.h" int git_refdb_new(git_refdb **out, git_repository *repo) { diff --git a/src/refdb.h b/src/refdb.h index 4ee3b8065..2d4ec753a 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_refdb_h__ #define INCLUDE_refdb_h__ +#include "common.h" + #include "git2/refdb.h" #include "repository.h" diff --git a/src/refdb_fs.c b/src/refdb_fs.c index eb135dc01..140879d23 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "refdb_fs.h" + #include "refs.h" #include "hash.h" #include "repository.h" @@ -13,7 +15,6 @@ #include "pack.h" #include "reflog.h" #include "refdb.h" -#include "refdb_fs.h" #include "iterator.h" #include "sortedcache.h" #include "signature.h" @@ -2034,6 +2035,7 @@ int git_refdb_backend_fs( if ((!git_repository__cvar(&t, backend->repo, GIT_CVAR_FSYNCOBJECTFILES) && t) || git_repository__fsync_gitdir) backend->fsync = 1; + backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS; backend->parent.exists = &refdb_fs_backend__exists; backend->parent.lookup = &refdb_fs_backend__lookup; diff --git a/src/refdb_fs.h b/src/refdb_fs.h index 79e296833..0c84814df 100644 --- a/src/refdb_fs.h +++ b/src/refdb_fs.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_refdb_fs_h__ #define INCLUDE_refdb_fs_h__ +#include "common.h" + +#include "strmap.h" + typedef struct { git_strmap *packfile; time_t packfile_time; diff --git a/src/reflog.c b/src/reflog.c index 98ef1b669..938999218 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -6,6 +6,7 @@ */ #include "reflog.h" + #include "repository.h" #include "filebuf.h" #include "signature.h" diff --git a/src/reflog.h b/src/reflog.h index 2d31ae47d..8c3895952 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -8,6 +8,7 @@ #define INCLUDE_reflog_h__ #include "common.h" + #include "git2/reflog.h" #include "vector.h" @@ -37,4 +38,4 @@ GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) return (total - 1) - idx; } -#endif /* INCLUDE_reflog_h__ */ +#endif diff --git a/src/refs.c b/src/refs.c index f7120d9ee..550963e56 100644 --- a/src/refs.c +++ b/src/refs.c @@ -6,6 +6,7 @@ */ #include "refs.h" + #include "hash.h" #include "repository.h" #include "fileops.h" @@ -1359,7 +1360,13 @@ int git_reference_peel( return peel_error(error, ref, "Cannot resolve reference"); } - if (!git_oid_iszero(&resolved->peel)) { + /* + * If we try to peel an object to a tag, we cannot use + * the fully peeled object, as that will always resolve + * to a commit. So we only want to use the peeled value + * if it is not zero and the target is not a tag. + */ + if (target_type != GIT_OBJ_TAG && !git_oid_iszero(&resolved->peel)) { error = git_object_lookup(&target, git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY); } else { diff --git a/src/refs.h b/src/refs.h index 0c90db3af..5149c7547 100644 --- a/src/refs.h +++ b/src/refs.h @@ -8,6 +8,7 @@ #define INCLUDE_refs_h__ #include "common.h" + #include "git2/oid.h" #include "git2/refs.h" #include "git2/refdb.h" @@ -29,7 +30,7 @@ extern bool git_reference__enable_symbolic_ref_target_validation; #define GIT_SYMREF "ref: " #define GIT_PACKEDREFS_FILE "packed-refs" -#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled " +#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled sorted " #define GIT_PACKEDREFS_FILE_MODE 0666 #define GIT_HEAD_FILE "HEAD" diff --git a/src/refspec.c b/src/refspec.c index d200e5609..01a77c97f 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -5,10 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "refspec.h" + #include "git2/errors.h" -#include "common.h" -#include "refspec.h" #include "util.h" #include "posix.h" #include "refs.h" diff --git a/src/refspec.h b/src/refspec.h index 9a87c97a5..fd2d8b312 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_refspec_h__ #define INCLUDE_refspec_h__ +#include "common.h" + #include "git2/refspec.h" #include "buffer.h" #include "vector.h" diff --git a/src/remote.c b/src/remote.c index bd8b3cfbc..4d675af82 100644 --- a/src/remote.c +++ b/src/remote.c @@ -5,15 +5,15 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "remote.h" + #include "git2/config.h" #include "git2/types.h" #include "git2/oid.h" #include "git2/net.h" -#include "common.h" #include "config.h" #include "repository.h" -#include "remote.h" #include "fetch.h" #include "refs.h" #include "refspec.h" @@ -197,10 +197,10 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n git_buf var = GIT_BUF_INIT; int error = -1; - /* name is optional */ - assert(out && repo && url); + /* repo, name, and fetch are optional */ + assert(out && url); - if ((error = git_repository_config_snapshot(&config_ro, repo)) < 0) + if (repo && (error = git_repository_config_snapshot(&config_ro, repo)) < 0) return error; remote = git__calloc(1, sizeof(git_remote)); @@ -212,7 +212,11 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n (error = canonicalize_url(&canonical_url, url)) < 0) goto on_error; - remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH); + if (repo) { + remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH); + } else { + remote->url = git__strdup(canonical_url.ptr); + } GITERR_CHECK_ALLOC(remote->url); if (name != NULL) { @@ -222,8 +226,9 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0) goto on_error; - if ((error = git_repository_config__weakptr(&config_rw, repo)) < 0 || - (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0) + if (repo && + ((error = git_repository_config__weakptr(&config_rw, repo)) < 0 || + (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)) goto on_error; } @@ -235,7 +240,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n if (name && (error = write_add_refspec(repo, name, fetch, true)) < 0) goto on_error; - if ((error = lookup_remote_prune_config(remote, config_ro, name)) < 0) + if (repo && (error = lookup_remote_prune_config(remote, config_ro, name)) < 0) goto on_error; /* Move the data over to where the matching functions can find them */ @@ -330,6 +335,11 @@ int git_remote_create_anonymous(git_remote **out, git_repository *repo, const ch return create_internal(out, repo, NULL, url, NULL); } +int git_remote_create_detached(git_remote **out, const char *url) +{ + return create_internal(out, NULL, NULL, url, NULL); +} + int git_remote_dup(git_remote **dest, git_remote *source) { size_t i; @@ -674,7 +684,9 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re url = git_remote__urlfordirection(remote, direction); if (url == NULL) { giterr_set(GITERR_INVALID, - "Malformed remote '%s' - missing URL", remote->name); + "Malformed remote '%s' - missing %s URL", + remote->name ? remote->name : "(anonymous)", + direction == GIT_DIRECTION_FETCH ? "fetch" : "push"); return -1; } @@ -859,6 +871,11 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const assert(remote); + if (!remote->repo) { + giterr_set(GITERR_INVALID, "cannot download detached remote"); + return -1; + } + if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; @@ -1524,6 +1541,20 @@ cleanup: return error; } +static int truncate_fetch_head(const char *gitdir) +{ + git_buf path = GIT_BUF_INIT; + int error; + + if ((error = git_buf_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0) + return error; + + error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE); + git_buf_free(&path); + + return error; +} + int git_remote_update_tips( git_remote *remote, const git_remote_callbacks *callbacks, @@ -1554,6 +1585,9 @@ int git_remote_update_tips( else tagopt = download_tags; + if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0) + goto out; + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) goto out; @@ -2344,6 +2378,11 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi assert(remote); + if (!remote->repo) { + giterr_set(GITERR_INVALID, "cannot download detached remote"); + return -1; + } + if (opts) { cbs = &opts->callbacks; custom_headers = &opts->custom_headers; @@ -2403,6 +2442,13 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ const git_strarray *custom_headers = NULL; const git_proxy_options *proxy = NULL; + assert(remote); + + if (!remote->repo) { + giterr_set(GITERR_INVALID, "cannot download detached remote"); + return -1; + } + if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; diff --git a/src/remote.h b/src/remote.h index e696997f4..a94481f25 100644 --- a/src/remote.h +++ b/src/remote.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_remote_h__ #define INCLUDE_remote_h__ +#include "common.h" + #include "git2/remote.h" #include "git2/transport.h" #include "git2/sys/transport.h" diff --git a/src/repository.c b/src/repository.c index 7ecb00ed8..90b778e03 100644 --- a/src/repository.c +++ b/src/repository.c @@ -4,14 +4,15 @@ * 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 "repository.h" + #include #include "git2/object.h" -#include "git2/refdb.h" #include "git2/sys/repository.h" #include "common.h" -#include "repository.h" #include "commit.h" #include "tag.h" #include "blob.h" @@ -23,6 +24,7 @@ #include "refs.h" #include "filter.h" #include "odb.h" +#include "refdb.h" #include "remote.h" #include "merge.h" #include "diff_driver.h" @@ -944,7 +946,7 @@ static int load_config( return error; if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) - error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0); + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); if (error && error != GIT_ENOTFOUND) goto on_error; @@ -953,25 +955,25 @@ static int load_config( if (global_config_path != NULL && (error = git_config_add_file_ondisk( - cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 && + cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, repo, 0)) < 0 && error != GIT_ENOTFOUND) goto on_error; if (xdg_config_path != NULL && (error = git_config_add_file_ondisk( - cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 && + cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, repo, 0)) < 0 && error != GIT_ENOTFOUND) goto on_error; if (system_config_path != NULL && (error = git_config_add_file_ondisk( - cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 && + cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, repo, 0)) < 0 && error != GIT_ENOTFOUND) goto on_error; if (programdata_path != NULL && (error = git_config_add_file_ondisk( - cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, 0)) < 0 && + cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, repo, 0)) < 0 && error != GIT_ENOTFOUND) goto on_error; @@ -1235,7 +1237,7 @@ static int reserved_names_add8dot3(git_repository *repo, const char *path) name_len = strlen(name); - if ((name_len == def_len && memcmp(name, def, def_len) == 0) || + if ((name_len == def_len && memcmp(name, def, def_len) == 0) || (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { git__free(name); return 0; @@ -1473,7 +1475,7 @@ static int repo_local_config( giterr_clear(); if (!(error = git_config_add_file_ondisk( - parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false))) + parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, repo, false))) error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); } @@ -1784,7 +1786,13 @@ static int repo_init_structure( default_template = true; } - if (tdir) { + /* + * If tdir was the empty string, treat it like tdir was a path to an + * empty directory (so, don't do any copying). This is the behavior + * that git(1) exhibits, although it doesn't seem to be officially + * documented. + */ + if (tdir && git__strcmp(tdir, "") != 0) { uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE | GIT_CPDIR_COPY_DOTFILES; @@ -2248,7 +2256,7 @@ int git_repository_is_empty(git_repository *repo) return is_empty; } -int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item) +int git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item) { const char *parent; @@ -2288,13 +2296,13 @@ int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_ return 0; } -const char *git_repository_path(git_repository *repo) +const char *git_repository_path(const git_repository *repo) { assert(repo); return repo->gitdir; } -const char *git_repository_workdir(git_repository *repo) +const char *git_repository_workdir(const git_repository *repo) { assert(repo); @@ -2304,7 +2312,7 @@ const char *git_repository_workdir(git_repository *repo) return repo->workdir; } -const char *git_repository_commondir(git_repository *repo) +const char *git_repository_commondir(const git_repository *repo) { assert(repo); return repo->commondir; @@ -2354,13 +2362,13 @@ int git_repository_set_workdir( return error; } -int git_repository_is_bare(git_repository *repo) +int git_repository_is_bare(const git_repository *repo) { assert(repo); return repo->is_bare; } -int git_repository_is_worktree(git_repository *repo) +int git_repository_is_worktree(const git_repository *repo) { assert(repo); return repo->is_worktree; @@ -2762,7 +2770,7 @@ int git_repository__cleanup_files( error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); } - + git_buf_clear(&buf); } diff --git a/src/repository.h b/src/repository.h index 52f9ec260..fd6400cc1 100644 --- a/src/repository.h +++ b/src/repository.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_repository_h__ #define INCLUDE_repository_h__ +#include "common.h" + #include "git2/common.h" #include "git2/oid.h" #include "git2/odb.h" diff --git a/src/reset.c b/src/reset.c index 066b5dbda..21596812f 100644 --- a/src/reset.c +++ b/src/reset.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "commit.h" #include "tag.h" #include "merge.h" @@ -20,8 +21,8 @@ int git_reset_default( git_repository *repo, - git_object *target, - git_strarray* pathspecs) + const git_object *target, + const git_strarray* pathspecs) { git_object *commit = NULL; git_tree *tree = NULL; @@ -100,7 +101,7 @@ cleanup: static int reset( git_repository *repo, - git_object *target, + const git_object *target, const char *to, git_reset_t reset_type, const git_checkout_options *checkout_opts) @@ -182,7 +183,7 @@ cleanup: int git_reset( git_repository *repo, - git_object *target, + const git_object *target, git_reset_t reset_type, const git_checkout_options *checkout_opts) { @@ -191,7 +192,7 @@ int git_reset( int git_reset_from_annotated( git_repository *repo, - git_annotated_commit *commit, + const git_annotated_commit *commit, git_reset_t reset_type, const git_checkout_options *checkout_opts) { diff --git a/src/revert.c b/src/revert.c index 747938fb3..54f6d48e4 100644 --- a/src/revert.c +++ b/src/revert.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "repository.h" #include "filebuf.h" #include "merge.h" diff --git a/src/revparse.c b/src/revparse.c index fd6bd1ea6..7cb22f476 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -5,9 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include -#include "common.h" #include "buffer.h" #include "tree.h" #include "refdb.h" @@ -769,7 +770,6 @@ int revparse__ext( } case '@': - { if (spec[pos+1] == '{') { git_object *temp_object = NULL; @@ -785,10 +785,8 @@ int revparse__ext( if (temp_object != NULL) base_rev = temp_object; break; - } else { - /* Fall through */ } - } + /* fall through */ default: if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0) diff --git a/src/revwalk.c b/src/revwalk.c index 77fa9fd0c..b1aa8f91a 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -5,12 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "revwalk.h" + #include "commit.h" #include "odb.h" #include "pool.h" -#include "revwalk.h" #include "git2/revparse.h" #include "merge.h" #include "vector.h" diff --git a/src/revwalk.h b/src/revwalk.h index 6b363d40f..578328b72 100644 --- a/src/revwalk.h +++ b/src/revwalk.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_revwalk_h__ #define INCLUDE_revwalk_h__ +#include "common.h" + #include "git2/revwalk.h" #include "oidmap.h" #include "commit_list.h" diff --git a/src/settings.c b/src/settings.c index 52b861ba0..2a52ffbf6 100644 --- a/src/settings.c +++ b/src/settings.c @@ -5,12 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #ifdef GIT_OPENSSL # include #endif #include -#include "common.h" #include "sysdir.h" #include "cache.h" #include "global.h" @@ -18,6 +19,7 @@ #include "odb.h" #include "refs.h" #include "transports/smart.h" +#include "streams/openssl.h" void git_libgit2_version(int *major, int *minor, int *rev) { @@ -171,14 +173,10 @@ int git_libgit2_opts(int key, ...) { const char *file = va_arg(ap, const char *); const char *path = va_arg(ap, const char *); - if (!SSL_CTX_load_verify_locations(git__ssl_ctx, file, path)) { - giterr_set(GITERR_NET, "SSL error: %s", - ERR_error_string(ERR_get_error(), NULL)); - error = -1; - } + error = git_openssl__set_cert_location(file, path); } #else - giterr_set(GITERR_NET, "cannot set certificate locations: OpenSSL is not enabled"); + giterr_set(GITERR_SSL, "TLS backend doesn't support certificate locations"); error = -1; #endif break; @@ -211,7 +209,7 @@ int git_libgit2_opts(int key, ...) } } #else - giterr_set(GITERR_NET, "cannot set custom ciphers: OpenSSL is not enabled"); + giterr_set(GITERR_SSL, "TLS backend doesn't support custom ciphers"); error = -1; #endif break; diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index ead26de06..14fcb40e5 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -5,226 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "sha1_lookup.h" + #include -#include "sha1_lookup.h" -#include "common.h" #include "oid.h" -/* - * Conventional binary search loop looks like this: - * - * unsigned lo, hi; - * do { - * unsigned mi = (lo + hi) / 2; - * int cmp = "entry pointed at by mi" minus "target"; - * if (!cmp) - * return (mi is the wanted one) - * if (cmp > 0) - * hi = mi; "mi is larger than target" - * else - * lo = mi+1; "mi is smaller than target" - * } while (lo < hi); - * - * The invariants are: - * - * - When entering the loop, lo points at a slot that is never - * above the target (it could be at the target), hi points at a - * slot that is guaranteed to be above the target (it can never - * be at the target). - * - * - We find a point 'mi' between lo and hi (mi could be the same - * as lo, but never can be as same as hi), and check if it hits - * the target. There are three cases: - * - * - if it is a hit, we are happy. - * - * - if it is strictly higher than the target, we set it to hi, - * and repeat the search. - * - * - if it is strictly lower than the target, we update lo to - * one slot after it, because we allow lo to be at the target. - * - * If the loop exits, there is no matching entry. - * - * When choosing 'mi', we do not have to take the "middle" but - * anywhere in between lo and hi, as long as lo <= mi < hi is - * satisfied. When we somehow know that the distance between the - * target and lo is much shorter than the target and hi, we could - * pick mi that is much closer to lo than the midway. - * - * Now, we can take advantage of the fact that SHA-1 is a good hash - * function, and as long as there are enough entries in the table, we - * can expect uniform distribution. An entry that begins with for - * example "deadbeef..." is much likely to appear much later than in - * the midway of the table. It can reasonably be expected to be near - * 87% (222/256) from the top of the table. - * - * However, we do not want to pick "mi" too precisely. If the entry at - * the 87% in the above example turns out to be higher than the target - * we are looking for, we would end up narrowing the search space down - * only by 13%, instead of 50% we would get if we did a simple binary - * search. So we would want to hedge our bets by being less aggressive. - * - * The table at "table" holds at least "nr" entries of "elem_size" - * bytes each. Each entry has the SHA-1 key at "key_offset". The - * table is sorted by the SHA-1 key of the entries. The caller wants - * to find the entry with "key", and knows that the entry at "lo" is - * not higher than the entry it is looking for, and that the entry at - * "hi" is higher than the entry it is looking for. - */ -int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key) -{ - const unsigned char *base = (const unsigned char*)table; - const unsigned char *hi_key, *lo_key; - unsigned ofs_0; - - if (!nr || lo >= hi) - return -1; - - if (nr == hi) - hi_key = NULL; - else - hi_key = base + elem_size * hi + key_offset; - lo_key = base + elem_size * lo + key_offset; - - ofs_0 = 0; - do { - int cmp; - unsigned ofs, mi, range; - unsigned lov, hiv, kyv; - const unsigned char *mi_key; - - range = hi - lo; - if (hi_key) { - for (ofs = ofs_0; ofs < 20; ofs++) - if (lo_key[ofs] != hi_key[ofs]) - break; - ofs_0 = ofs; - /* - * byte 0 thru (ofs-1) are the same between - * lo and hi; ofs is the first byte that is - * different. - * - * If ofs==20, then no bytes are different, - * meaning we have entries with duplicate - * keys. We know that we are in a solid run - * of this entry (because the entries are - * sorted, and our lo and hi are the same, - * there can be nothing but this single key - * in between). So we can stop the search. - * Either one of these entries is it (and - * we do not care which), or we do not have - * it. - * - * Furthermore, we know that one of our - * endpoints must be the edge of the run of - * duplicates. For example, given this - * sequence: - * - * idx 0 1 2 3 4 5 - * key A C C C C D - * - * If we are searching for "B", we might - * hit the duplicate run at lo=1, hi=3 - * (e.g., by first mi=3, then mi=0). But we - * can never have lo > 1, because B < C. - * That is, if our key is less than the - * run, we know that "lo" is the edge, but - * we can say nothing of "hi". Similarly, - * if our key is greater than the run, we - * know that "hi" is the edge, but we can - * say nothing of "lo". - * - * Therefore if we do not find it, we also - * know where it would go if it did exist: - * just on the far side of the edge that we - * know about. - */ - if (ofs == 20) { - mi = lo; - mi_key = base + elem_size * mi + key_offset; - cmp = memcmp(mi_key, key, 20); - if (!cmp) - return mi; - if (cmp < 0) - return -1 - hi; - else - return -1 - lo; - } - - hiv = hi_key[ofs_0]; - if (ofs_0 < 19) - hiv = (hiv << 8) | hi_key[ofs_0+1]; - } else { - hiv = 256; - if (ofs_0 < 19) - hiv <<= 8; - } - lov = lo_key[ofs_0]; - kyv = key[ofs_0]; - if (ofs_0 < 19) { - lov = (lov << 8) | lo_key[ofs_0+1]; - kyv = (kyv << 8) | key[ofs_0+1]; - } - assert(lov < hiv); - - if (kyv < lov) - return -1 - lo; - if (hiv < kyv) - return -1 - hi; - - /* - * Even if we know the target is much closer to 'hi' - * than 'lo', if we pick too precisely and overshoot - * (e.g. when we know 'mi' is closer to 'hi' than to - * 'lo', pick 'mi' that is higher than the target), we - * end up narrowing the search space by a smaller - * amount (i.e. the distance between 'mi' and 'hi') - * than what we would have (i.e. about half of 'lo' - * and 'hi'). Hedge our bets to pick 'mi' less - * aggressively, i.e. make 'mi' a bit closer to the - * middle than we would otherwise pick. - */ - kyv = (kyv * 6 + lov + hiv) / 8; - if (lov < hiv - 1) { - if (kyv == lov) - kyv++; - else if (kyv == hiv) - kyv--; - } - mi = (range - 1) * (kyv - lov) / (hiv - lov) + lo; - -#ifdef INDEX_DEBUG_LOOKUP - printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi); - printf("ofs %u lov %x, hiv %x, kyv %x\n", - ofs_0, lov, hiv, kyv); -#endif - - if (!(lo <= mi && mi < hi)) { - giterr_set(GITERR_INVALID, "assertion failure: binary search invariant is false"); - return -1; - } - - mi_key = base + elem_size * mi + key_offset; - cmp = memcmp(mi_key + ofs_0, key + ofs_0, 20 - ofs_0); - if (!cmp) - return mi; - if (cmp > 0) { - hi = mi; - hi_key = mi_key; - } else { - lo = mi + 1; - lo_key = mi_key + elem_size; - } - } while (lo < hi); - return -((int)lo)-1; -} - int sha1_position(const void *table, size_t stride, unsigned lo, unsigned hi, @@ -232,7 +18,7 @@ int sha1_position(const void *table, { const unsigned char *base = table; - do { + while (lo < hi) { unsigned mi = (lo + hi) / 2; int cmp = git_oid__hashcmp(base + mi * stride, key); @@ -243,7 +29,7 @@ int sha1_position(const void *table, hi = mi; else lo = mi+1; - } while (lo < hi); + } return -((int)lo)-1; } diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h index 3799620c7..841ea5b22 100644 --- a/src/sha1_lookup.h +++ b/src/sha1_lookup.h @@ -7,13 +7,9 @@ #ifndef INCLUDE_sha1_lookup_h__ #define INCLUDE_sha1_lookup_h__ -#include +#include "common.h" -int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key); +#include int sha1_position(const void *table, size_t stride, diff --git a/src/signature.c b/src/signature.c index a56b8a299..cd6852326 100644 --- a/src/signature.c +++ b/src/signature.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "signature.h" + #include "repository.h" #include "git2/common.h" #include "posix.h" @@ -90,6 +90,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema p->when.time = time; p->when.offset = offset; + p->when.sign = (offset < 0) ? '-' : '+'; *sig_out = p; return 0; @@ -113,6 +114,7 @@ int git_signature_dup(git_signature **dest, const git_signature *source) signature->when.time = source->when.time; signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; *dest = signature; @@ -137,6 +139,7 @@ int git_signature__pdup(git_signature **dest, const git_signature *source, git_p signature->when.time = source->when.time; signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; *dest = signature; @@ -231,6 +234,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0) { git__free(sig->name); git__free(sig->email); + sig->name = sig->email = NULL; return signature_error("invalid Unix timestamp"); } @@ -256,6 +260,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, */ if (hours <= 14 && mins <= 59) { sig->when.offset = (hours * 60) + mins; + sig->when.sign = tz_start[0]; if (tz_start[0] == '-') sig->when.offset = -sig->when.offset; } @@ -298,7 +303,7 @@ void git_signature__writebuf(git_buf *buf, const char *header, const git_signatu assert(buf && sig); offset = sig->when.offset; - sign = (sig->when.offset < 0) ? '-' : '+'; + sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+'; if (offset < 0) offset = -offset; @@ -319,6 +324,7 @@ bool git_signature__equal(const git_signature *one, const git_signature *two) git__strcmp(one->name, two->name) == 0 && git__strcmp(one->email, two->email) == 0 && one->when.time == two->when.time && - one->when.offset == two->when.offset; + one->when.offset == two->when.offset && + one->when.sign == two->when.sign; } diff --git a/src/signature.h b/src/signature.h index 75265df52..40d7c54f9 100644 --- a/src/signature.h +++ b/src/signature.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_signature_h__ #define INCLUDE_signature_h__ +#include "common.h" + #include "git2/common.h" #include "git2/signature.h" #include "repository.h" diff --git a/src/sortedcache.c b/src/sortedcache.c index cc322d478..76672d9f7 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -1,3 +1,10 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "sortedcache.h" int git_sortedcache_new( diff --git a/src/sortedcache.h b/src/sortedcache.h index 4cacad62b..a53ff48a3 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_sorted_cache_h__ #define INCLUDE_sorted_cache_h__ +#include "common.h" + #include "util.h" #include "fileops.h" #include "vector.h" diff --git a/src/stash.c b/src/stash.c index d13220cdd..8a8c57a83 100644 --- a/src/stash.c +++ b/src/stash.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "repository.h" #include "commit.h" #include "message.h" diff --git a/src/status.c b/src/status.c index 6752b5625..f547bd466 100644 --- a/src/status.c +++ b/src/status.c @@ -5,13 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "status.h" + #include "git2.h" #include "fileops.h" #include "hash.h" #include "vector.h" #include "tree.h" -#include "status.h" #include "git2/status.h" #include "repository.h" #include "ignore.h" @@ -280,12 +280,16 @@ int git_status_list_new( if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || (error = git_repository_index(&index, repo)) < 0) return error; - - /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if ((error = git_repository_head_tree(&head, repo)) < 0) { - if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) - goto done; - giterr_clear(); + + if (opts != NULL && opts->baseline != NULL) { + head = opts->baseline; + } else { + /* if there is no HEAD, that's okay - we'll make an empty iterator */ + if ((error = git_repository_head_tree(&head, repo)) < 0) { + if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) + goto done; + giterr_clear(); + } } /* refresh index from disk unless prevented */ @@ -377,7 +381,8 @@ done: *out = status; - git_tree_free(head); + if (opts == NULL || opts->baseline != head) + git_tree_free(head); git_index_free(index); return error; diff --git a/src/status.h b/src/status.h index 33008b89c..907479a22 100644 --- a/src/status.h +++ b/src/status.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_status_h__ #define INCLUDE_status_h__ +#include "common.h" + #include "diff.h" #include "git2/status.h" #include "git2/diff.h" diff --git a/src/curl_stream.c b/src/streams/curl.c similarity index 94% rename from src/curl_stream.c rename to src/streams/curl.c index 4e0455cca..ee13be1dc 100644 --- a/src/curl_stream.c +++ b/src/streams/curl.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "streams/curl.h" + #ifdef GIT_CURL #include @@ -12,6 +14,7 @@ #include "stream.h" #include "git2/transport.h" #include "buffer.h" +#include "global.h" #include "vector.h" #include "proxy.h" @@ -36,6 +39,18 @@ typedef struct { git_cred *proxy_cred; } curl_stream; +int git_curl_stream_global_init(void) +{ + if (curl_global_init(CURL_GLOBAL_ALL) != 0) { + giterr_set(GITERR_NET, "could not initialize curl"); + return -1; + } + + /* `curl_global_cleanup` is provided by libcurl */ + git__on_shutdown(curl_global_cleanup); + return 0; +} + static int seterr_curl(curl_stream *s) { giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error); @@ -193,6 +208,7 @@ static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_op CURLcode res; curl_stream *s = (curl_stream *) stream; + git_proxy_options_clear(&s->proxy); if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0) return error; @@ -293,6 +309,8 @@ static void curls_free(git_stream *stream) curls_close(stream); git_strarray_free(&s->cert_info_strings); + git_proxy_options_clear(&s->proxy); + git_cred_free(s->proxy_cred); git__free(s); } @@ -348,6 +366,11 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port) #include "stream.h" +int git_curl_stream_global_init(void) +{ + return 0; +} + int git_curl_stream_new(git_stream **out, const char *host, const char *port) { GIT_UNUSED(out); diff --git a/src/curl_stream.h b/src/streams/curl.h similarity index 71% rename from src/curl_stream.h rename to src/streams/curl.h index 283f0fe40..511cd894a 100644 --- a/src/curl_stream.h +++ b/src/streams/curl.h @@ -4,11 +4,14 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_curl_stream_h__ -#define INCLUDE_curl_stream_h__ +#ifndef INCLUDE_streams_curl_h__ +#define INCLUDE_streams_curl_h__ + +#include "common.h" #include "git2/sys/stream.h" +extern int git_curl_stream_global_init(void); extern int git_curl_stream_new(git_stream **out, const char *host, const char *port); #endif diff --git a/src/openssl_stream.c b/src/streams/openssl.c similarity index 90% rename from src/openssl_stream.c rename to src/streams/openssl.c index 759c5015f..9cbb2746f 100644 --- a/src/openssl_stream.c +++ b/src/streams/openssl.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "streams/openssl.h" + #ifdef GIT_OPENSSL #include @@ -12,14 +14,13 @@ #include "global.h" #include "posix.h" #include "stream.h" -#include "socket_stream.h" -#include "openssl_stream.h" +#include "streams/socket.h" #include "netops.h" #include "git2/transport.h" #include "git2/sys/openssl.h" #ifdef GIT_CURL -# include "curl_stream.h" +# include "streams/curl.h" #endif #ifndef GIT_WIN32 @@ -149,11 +150,20 @@ int git_openssl_stream_global_init(void) return 0; } +#if defined(GIT_THREADS) +static void threadid_cb(CRYPTO_THREADID *threadid) +{ + CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); +} +#endif + int git_openssl_set_locking(void) { #if defined(GIT_THREADS) && OPENSSL_VERSION_NUMBER < 0x10100000L int num_locks, i; + CRYPTO_THREADID_set_callback(threadid_cb); + num_locks = CRYPTO_num_locks(); openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); GITERR_CHECK_ALLOC(openssl_locks); @@ -272,8 +282,9 @@ static int ssl_set_error(SSL *ssl, int error) case SSL_ERROR_SYSCALL: e = ERR_get_error(); if (e > 0) { - giterr_set(GITERR_NET, "SSL error: %s", - ERR_error_string(e, NULL)); + char errmsg[256]; + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + giterr_set(GITERR_NET, "SSL error: %s", errmsg); break; } else if (error < 0) { giterr_set(GITERR_OS, "SSL error: syscall failure"); @@ -283,10 +294,13 @@ static int ssl_set_error(SSL *ssl, int error) return GIT_EEOF; break; case SSL_ERROR_SSL: + { + char errmsg[256]; e = ERR_get_error(); - giterr_set(GITERR_NET, "SSL error: %s", - ERR_error_string(e, NULL)); + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + giterr_set(GITERR_NET, "SSL error: %s", errmsg); break; + } case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: default: @@ -322,7 +336,7 @@ static int check_host_name(const char *name, const char *host) static int verify_server_cert(SSL *ssl, const char *host) { - X509 *cert; + X509 *cert = NULL; X509_NAME *peer_name; ASN1_STRING *str; unsigned char *peer_cn = NULL; @@ -330,8 +344,8 @@ static int verify_server_cert(SSL *ssl, const char *host) GENERAL_NAMES *alts; struct in6_addr addr6; struct in_addr addr4; - void *addr; - int i = -1,j; + void *addr = NULL; + int i = -1, j, error = 0; if (SSL_get_verify_result(ssl) != X509_V_OK) { giterr_set(GITERR_SSL, "the SSL certificate is invalid"); @@ -343,7 +357,7 @@ static int verify_server_cert(SSL *ssl, const char *host) type = GEN_IPADD; addr = &addr4; } else { - if(p_inet_pton(AF_INET6, host, &addr6)) { + if (p_inet_pton(AF_INET6, host, &addr6)) { type = GEN_IPADD; addr = &addr6; } @@ -352,8 +366,9 @@ static int verify_server_cert(SSL *ssl, const char *host) cert = SSL_get_peer_certificate(ssl); if (!cert) { + error = -1; giterr_set(GITERR_SSL, "the server did not provide a certificate"); - return -1; + goto cleanup; } /* Check the alternative names */ @@ -382,7 +397,7 @@ static int verify_server_cert(SSL *ssl, const char *host) matched = 1; } else if (type == GEN_IPADD) { /* Here name isn't so much a name but a binary representation of the IP */ - matched = !!memcmp(name, addr, namelen); + matched = addr && !!memcmp(name, addr, namelen); } } } @@ -391,8 +406,9 @@ static int verify_server_cert(SSL *ssl, const char *host) if (matched == 0) goto cert_fail_name; - if (matched == 1) - return 0; + if (matched == 1) { + goto cleanup; + } /* If no alternative names are available, check the common name */ peer_name = X509_get_subject_name(cert); @@ -434,18 +450,21 @@ static int verify_server_cert(SSL *ssl, const char *host) if (check_host_name((char *)peer_cn, host) < 0) goto cert_fail_name; - OPENSSL_free(peer_cn); - - return 0; - -on_error: - OPENSSL_free(peer_cn); - return ssl_set_error(ssl, 0); + goto cleanup; cert_fail_name: - OPENSSL_free(peer_cn); + error = GIT_ECERTIFICATE; giterr_set(GITERR_SSL, "hostname does not match certificate"); - return GIT_ECERTIFICATE; + goto cleanup; + +on_error: + error = ssl_set_error(ssl, 0); + goto cleanup; + +cleanup: + X509_free(cert); + OPENSSL_free(peer_cn); + return error; } typedef struct { @@ -627,6 +646,20 @@ out_err: return error; } +int git_openssl__set_cert_location(const char *file, const char *path) +{ + if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { + char errmsg[256]; + + ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); + giterr_set(GITERR_SSL, "OpenSSL error: failed to load certificates: %s", + errmsg); + + return -1; + } + return 0; +} + #else #include "stream.h" @@ -653,4 +686,13 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port) return -1; } +int git_openssl__set_cert_location(const char *file, const char *path) +{ + GIT_UNUSED(file); + GIT_UNUSED(path); + + giterr_set(GITERR_SSL, "openssl is not supported in this version"); + return -1; +} + #endif diff --git a/src/openssl_stream.h b/src/streams/openssl.h similarity index 93% rename from src/openssl_stream.h rename to src/streams/openssl.h index f5e59dab1..2bbad7c68 100644 --- a/src/openssl_stream.h +++ b/src/streams/openssl.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_openssl_stream_h__ -#define INCLUDE_openssl_stream_h__ +#ifndef INCLUDE_streams_openssl_h__ +#define INCLUDE_streams_openssl_h__ + +#include "common.h" #include "git2/sys/stream.h" @@ -13,6 +15,8 @@ extern int git_openssl_stream_global_init(void); extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); +extern int git_openssl__set_cert_location(const char *file, const char *path); + /* * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it * which do not exist in previous versions. We define these inline functions so diff --git a/src/socket_stream.c b/src/streams/socket.c similarity index 97% rename from src/socket_stream.c rename to src/streams/socket.c index c0a168448..0c6073b66 100644 --- a/src/socket_stream.c +++ b/src/streams/socket.c @@ -5,11 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "streams/socket.h" + #include "posix.h" #include "netops.h" #include "stream.h" -#include "socket_stream.h" #ifndef _WIN32 # include @@ -104,7 +104,7 @@ int socket_connect(git_stream *stream) } for (p = info; p != NULL; p = p->ai_next) { - s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); if (s == INVALID_SOCKET) continue; diff --git a/src/socket_stream.h b/src/streams/socket.h similarity index 82% rename from src/socket_stream.h rename to src/streams/socket.h index 8e9949fcd..3235f3167 100644 --- a/src/socket_stream.h +++ b/src/streams/socket.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_socket_stream_h__ -#define INCLUDE_socket_stream_h__ +#ifndef INCLUDE_streams_socket_h__ +#define INCLUDE_streams_socket_h__ + +#include "common.h" #include "netops.h" diff --git a/src/stransport_stream.c b/src/streams/stransport.c similarity index 97% rename from src/stransport_stream.c rename to src/streams/stransport.c index 50ed9452c..cca17bb94 100644 --- a/src/stransport_stream.c +++ b/src/streams/stransport.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "streams/stransport.h" + #ifdef GIT_SECURE_TRANSPORT #include @@ -13,8 +15,8 @@ #include "git2/transport.h" -#include "socket_stream.h" -#include "curl_stream.h" +#include "streams/socket.h" +#include "streams/curl.h" static int stransport_error(OSStatus ret) { @@ -81,8 +83,10 @@ static int stransport_connect(git_stream *stream) } if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure || - sec_res == kSecTrustResultFatalTrustFailure) + sec_res == kSecTrustResultFatalTrustFailure) { + giterr_set(GITERR_SSL, "untrusted connection error"); return GIT_ECERTIFICATE; + } return 0; diff --git a/src/stransport_stream.h b/src/streams/stransport.h similarity index 77% rename from src/stransport_stream.h rename to src/streams/stransport.h index 714f90273..4c02d07e8 100644 --- a/src/stransport_stream.h +++ b/src/streams/stransport.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_stransport_stream_h__ -#define INCLUDE_stransport_stream_h__ +#ifndef INCLUDE_streams_stransport_h__ +#define INCLUDE_streams_stransport_h__ + +#include "common.h" #include "git2/sys/stream.h" diff --git a/src/tls_stream.c b/src/streams/tls.c similarity index 90% rename from src/tls_stream.c rename to src/streams/tls.c index 83e2d064a..d6ca7d40d 100644 --- a/src/tls_stream.c +++ b/src/streams/tls.c @@ -5,11 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/errors.h" -#include "common.h" +#include "streams/tls.h" -#include "openssl_stream.h" -#include "stransport_stream.h" +#include "git2/errors.h" + +#include "streams/openssl.h" +#include "streams/stransport.h" static git_stream_cb tls_ctor; diff --git a/src/tls_stream.h b/src/streams/tls.h similarity index 86% rename from src/tls_stream.h rename to src/streams/tls.h index 98a704174..6d110e8ad 100644 --- a/src/tls_stream.h +++ b/src/streams/tls.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_tls_stream_h__ -#define INCLUDE_tls_stream_h__ +#ifndef INCLUDE_streams_tls_h__ +#define INCLUDE_streams_tls_h__ + +#include "common.h" #include "git2/sys/stream.h" diff --git a/src/submodule.c b/src/submodule.c index ddd4b0663..3ec0307b3 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "submodule.h" + #include "git2/config.h" #include "git2/sys/config.h" #include "git2/types.h" @@ -17,7 +18,6 @@ #include "config_file.h" #include "config.h" #include "repository.h" -#include "submodule.h" #include "tree.h" #include "iterator.h" #include "path.h" @@ -209,6 +209,11 @@ int git_submodule_lookup( assert(repo && name); + if (repo->is_bare) { + giterr_set(GITERR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + if (repo->submodule_cache != NULL) { khiter_t pos = git_strmap_lookup_index(repo->submodule_cache, name); if (git_strmap_valid_index(repo->submodule_cache, pos)) { @@ -549,6 +554,11 @@ int git_submodule_foreach( int error; size_t i; + if (repo->is_bare) { + giterr_set(GITERR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + if ((error = git_strmap_alloc(&submodules)) < 0) return error; @@ -1950,7 +1960,7 @@ static git_config_backend *open_gitmodules( if (git_config_file__ondisk(&mods, path.ptr) < 0) mods = NULL; /* open should only fail here if the file is malformed */ - else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) { + else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) { git_config_file_free(mods); mods = NULL; } diff --git a/src/submodule.h b/src/submodule.h index 456a93979..72867a322 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_submodule_h__ #define INCLUDE_submodule_h__ +#include "common.h" + #include "git2/submodule.h" #include "git2/repository.h" #include "fileops.h" diff --git a/src/sysdir.c b/src/sysdir.c index 9312a7edb..509b23b82 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -5,14 +5,17 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "sysdir.h" + #include "global.h" #include "buffer.h" #include "path.h" #include #if GIT_WIN32 #include "win32/findfile.h" +#else +#include +#include #endif static int git_sysdir_guess_programdata_dirs(git_buf *out) @@ -34,12 +37,63 @@ static int git_sysdir_guess_system_dirs(git_buf *out) #endif } +#ifndef GIT_WIN32 +static int get_passwd_home(git_buf *out, uid_t uid) +{ + struct passwd pwd, *pwdptr; + char *buf = NULL; + long buflen; + int error; + + assert(out); + + if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) + buflen = 1024; + + do { + buf = git__realloc(buf, buflen); + error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr); + buflen *= 2; + } while (error == ERANGE && buflen <= 8192); + + if (error) { + giterr_set(GITERR_OS, "failed to get passwd entry"); + goto out; + } + + if (!pwdptr) { + giterr_set(GITERR_OS, "no passwd entry found for user"); + goto out; + } + + if ((error = git_buf_puts(out, pwdptr->pw_dir)) < 0) + goto out; + +out: + git__free(buf); + return error; +} +#endif + static int git_sysdir_guess_global_dirs(git_buf *out) { #ifdef GIT_WIN32 return git_win32__find_global_dirs(out); #else - int error = git__getenv(out, "HOME"); + int error; + uid_t uid, euid; + + uid = getuid(); + euid = geteuid(); + + /* + * In case we are running setuid, use the configuration + * of the effective user. + */ + if (uid == euid) + error = git__getenv(out, "HOME"); + else + error = get_passwd_home(out, euid); if (error == GIT_ENOTFOUND) { giterr_clear(); @@ -57,12 +111,25 @@ static int git_sysdir_guess_xdg_dirs(git_buf *out) #else git_buf env = GIT_BUF_INIT; int error; + uid_t uid, euid; - if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) - error = git_buf_joinpath(out, env.ptr, "git"); + uid = getuid(); + euid = geteuid(); - if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) - error = git_buf_joinpath(out, env.ptr, ".config/git"); + /* + * In case we are running setuid, only look up passwd + * directory of the effective user. + */ + if (uid == euid) { + if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) + error = git_buf_joinpath(out, env.ptr, "git"); + + if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) + error = git_buf_joinpath(out, env.ptr, ".config/git"); + } else { + if ((error = get_passwd_home(&env, euid)) == 0) + error = git_buf_joinpath(out, env.ptr, ".config/git"); + } if (error == GIT_ENOTFOUND) { giterr_clear(); diff --git a/src/sysdir.h b/src/sysdir.h index 79f23818a..ce1b4dc71 100644 --- a/src/sysdir.h +++ b/src/sysdir.h @@ -8,6 +8,7 @@ #define INCLUDE_sysdir_h__ #include "common.h" + #include "posix.h" #include "buffer.h" @@ -115,4 +116,4 @@ extern int git_sysdir_get_str(char *out, size_t outlen, git_sysdir_t which); */ extern int git_sysdir_set(git_sysdir_t which, const char *paths); -#endif /* INCLUDE_sysdir_h__ */ +#endif diff --git a/src/tag.c b/src/tag.c index 2bf23fc3c..445c3ff1d 100644 --- a/src/tag.c +++ b/src/tag.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "commit.h" #include "tag.h" + +#include "commit.h" #include "signature.h" #include "message.h" #include "git2/object.h" diff --git a/src/tag.h b/src/tag.h index d0cd393c7..8aae37840 100644 --- a/src/tag.h +++ b/src/tag.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_tag_h__ #define INCLUDE_tag_h__ +#include "common.h" + #include "git2/tag.h" #include "repository.h" #include "odb.h" diff --git a/src/thread-utils.c b/src/thread-utils.c index dc9b2f09e..e5ec6a843 100644 --- a/src/thread-utils.c +++ b/src/thread-utils.c @@ -4,6 +4,7 @@ * 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 "common.h" #include "thread-utils.h" diff --git a/src/thread-utils.h b/src/thread-utils.h index 2df2aeb99..035de699f 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -243,4 +243,4 @@ extern int git_online_cpus(void); # define GIT_MEMORY_BARRIER /* noop */ #endif -#endif /* INCLUDE_thread_utils_h__ */ +#endif diff --git a/src/trace.c b/src/trace.c index 0f2142861..080d1e891 100644 --- a/src/trace.c +++ b/src/trace.c @@ -5,10 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "buffer.h" -#include "common.h" -#include "global.h" #include "trace.h" + +#include "buffer.h" +#include "global.h" #include "git2/trace.h" #ifdef GIT_TRACE diff --git a/src/trace.h b/src/trace.h index 486084d01..498944035 100644 --- a/src/trace.h +++ b/src/trace.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_trace_h__ #define INCLUDE_trace_h__ +#include "common.h" + #include #include "buffer.h" diff --git a/src/trailer.c b/src/trailer.c new file mode 100644 index 000000000..24c8847f6 --- /dev/null +++ b/src/trailer.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "array.h" +#include "common.h" +#include "git2/message.h" + +#include +#include +#include + +#define COMMENT_LINE_CHAR '#' +#define TRAILER_SEPARATORS ":" + +static const char *const git_generated_prefixes[] = { + "Signed-off-by: ", + "(cherry picked from commit ", + NULL +}; + +static int is_blank_line(const char *str) +{ + const char *s = str; + while (*s && *s != '\n' && isspace(*s)) + s++; + return !*s || *s == '\n'; +} + +static const char *next_line(const char *str) +{ + const char *nl = strchr(str, '\n'); + + if (nl) { + return nl + 1; + } else { + // return pointer to the NUL terminator: + return str + strlen(str); + } +} + +/* + * Return the position of the start of the last line. If len is 0, return -1. + */ +static int last_line(const char *buf, size_t len) +{ + int i; + if (len == 0) + return -1; + if (len == 1) + return 0; + /* + * Skip the last character (in addition to the null terminator), + * because if the last character is a newline, it is considered as part + * of the last line anyway. + */ + i = len - 2; + + for (; i >= 0; i--) { + if (buf[i] == '\n') + return i + 1; + } + return 0; +} + +/* + * If the given line is of the form + * "..." or "...", return the + * location of the separator. Otherwise, return -1. The optional whitespace + * is allowed there primarily to allow things like "Bug #43" where is + * "Bug" and is "#". + * + * The separator-starts-line case (in which this function returns 0) is + * distinguished from the non-well-formed-line case (in which this function + * returns -1) because some callers of this function need such a distinction. + */ +static int find_separator(const char *line, const char *separators) +{ + int whitespace_found = 0; + const char *c; + for (c = line; *c; c++) { + if (strchr(separators, *c)) + return c - line; + if (!whitespace_found && (isalnum(*c) || *c == '-')) + continue; + if (c != line && (*c == ' ' || *c == '\t')) { + whitespace_found = 1; + continue; + } + break; + } + return -1; +} + +/* + * Inspect the given string and determine the true "end" of the log message, in + * order to find where to put a new Signed-off-by: line. Ignored are + * trailing comment lines and blank lines. To support "git commit -s + * --amend" on an existing commit, we also ignore "Conflicts:". To + * support "git commit -v", we truncate at cut lines. + * + * Returns the number of bytes from the tail to ignore, to be fed as + * the second parameter to append_signoff(). + */ +static int ignore_non_trailer(const char *buf, size_t len) +{ + int boc = 0; + size_t bol = 0; + int in_old_conflicts_block = 0; + size_t cutoff = len; + + while (bol < cutoff) { + const char *next_line = memchr(buf + bol, '\n', len - bol); + + if (!next_line) + next_line = buf + len; + else + next_line++; + + if (buf[bol] == COMMENT_LINE_CHAR || buf[bol] == '\n') { + /* is this the first of the run of comments? */ + if (!boc) + boc = bol; + /* otherwise, it is just continuing */ + } else if (git__prefixcmp(buf + bol, "Conflicts:\n") == 0) { + in_old_conflicts_block = 1; + if (!boc) + boc = bol; + } else if (in_old_conflicts_block && buf[bol] == '\t') { + ; /* a pathname in the conflicts block */ + } else if (boc) { + /* the previous was not trailing comment */ + boc = 0; + in_old_conflicts_block = 0; + } + bol = next_line - buf; + } + return boc ? len - boc : len - cutoff; +} + +/* + * Return the position of the start of the patch or the length of str if there + * is no patch in the message. + */ +static int find_patch_start(const char *str) +{ + const char *s; + + for (s = str; *s; s = next_line(s)) { + if (git__prefixcmp(s, "---") == 0) + return s - str; + } + + return s - str; +} + +/* + * Return the position of the first trailer line or len if there are no + * trailers. + */ +static int find_trailer_start(const char *buf, size_t len) +{ + const char *s; + int end_of_title, l, only_spaces = 1; + int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; + /* + * Number of possible continuation lines encountered. This will be + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; + + /* The first paragraph is the title and cannot be trailers */ + for (s = buf; s < buf + len; s = next_line(s)) { + if (s[0] == COMMENT_LINE_CHAR) + continue; + if (is_blank_line(s)) + break; + } + end_of_title = s - buf; + + /* + * Get the start of the trailers by looking starting from the end for a + * blank line before a set of non-blank lines that (i) are all + * trailers, or (ii) contains at least one Git-generated trailer and + * consists of at least 25% trailers. + */ + for (l = last_line(buf, len); + l >= end_of_title; + l = last_line(buf, l)) { + const char *bol = buf + l; + const char *const *p; + int separator_pos; + + if (bol[0] == COMMENT_LINE_CHAR) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + continue; + } + if (is_blank_line(bol)) { + if (only_spaces) + continue; + non_trailer_lines += possible_continuation_lines; + if (recognized_prefix && + trailer_lines * 3 >= non_trailer_lines) + return next_line(bol) - buf; + else if (trailer_lines && !non_trailer_lines) + return next_line(bol) - buf; + return len; + } + only_spaces = 0; + + for (p = git_generated_prefixes; *p; p++) { + if (git__prefixcmp(bol, *p) == 0) { + trailer_lines++; + possible_continuation_lines = 0; + recognized_prefix = 1; + goto continue_outer_loop; + } + } + + separator_pos = find_separator(bol, TRAILER_SEPARATORS); + if (separator_pos >= 1 && !isspace(bol[0])) { + trailer_lines++; + possible_continuation_lines = 0; + if (recognized_prefix) + continue; + } else if (isspace(bol[0])) + possible_continuation_lines++; + else { + non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + } +continue_outer_loop: + ; + } + + return len; +} + +/* Return the position of the end of the trailers. */ +static int find_trailer_end(const char *buf, size_t len) +{ + return len - ignore_non_trailer(buf, len); +} + +static char *extract_trailer_block(const char *message, size_t* len) +{ + size_t patch_start = find_patch_start(message); + size_t trailer_end = find_trailer_end(message, patch_start); + size_t trailer_start = find_trailer_start(message, trailer_end); + + size_t trailer_len = trailer_end - trailer_start; + + char *buffer = git__malloc(trailer_len + 1); + memcpy(buffer, message + trailer_start, trailer_len); + buffer[trailer_len] = 0; + + *len = trailer_len; + + return buffer; +} + +enum trailer_state { + S_START = 0, + S_KEY = 1, + S_KEY_WS = 2, + S_SEP_WS = 3, + S_VALUE = 4, + S_VALUE_NL = 5, + S_VALUE_END = 6, + S_IGNORE = 7, +}; + +#define NEXT(st) { state = (st); ptr++; continue; } +#define GOTO(st) { state = (st); continue; } + +typedef git_array_t(git_message_trailer) git_array_trailer_t; + +int git_message_trailers(git_message_trailer_array *trailer_arr, const char *message) +{ + enum trailer_state state = S_START; + int rc = 0; + char *ptr; + char *key = NULL; + char *value = NULL; + git_array_trailer_t arr = GIT_ARRAY_INIT; + + size_t trailer_len; + char *trailer = extract_trailer_block(message, &trailer_len); + + for (ptr = trailer;;) { + switch (state) { + case S_START: { + if (*ptr == 0) { + goto ret; + } + + key = ptr; + GOTO(S_KEY); + } + case S_KEY: { + if (*ptr == 0) { + goto ret; + } + + if (isalnum(*ptr) || *ptr == '-') { + // legal key character + NEXT(S_KEY); + } + + if (*ptr == ' ' || *ptr == '\t') { + // optional whitespace before separator + *ptr = 0; + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + *ptr = 0; + NEXT(S_SEP_WS); + } + + // illegal character + GOTO(S_IGNORE); + } + case S_KEY_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + NEXT(S_SEP_WS); + } + + // illegal character + GOTO(S_IGNORE); + } + case S_SEP_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_SEP_WS); + } + + value = ptr; + NEXT(S_VALUE); + } + case S_VALUE: { + if (*ptr == 0) { + GOTO(S_VALUE_END); + } + + if (*ptr == '\n') { + NEXT(S_VALUE_NL); + } + + NEXT(S_VALUE); + } + case S_VALUE_NL: { + if (*ptr == ' ') { + // continuation; + NEXT(S_VALUE); + } + + ptr[-1] = 0; + GOTO(S_VALUE_END); + } + case S_VALUE_END: { + git_message_trailer *t = git_array_alloc(arr); + + t->key = key; + t->value = value; + + key = NULL; + value = NULL; + + GOTO(S_START); + } + case S_IGNORE: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == '\n') { + NEXT(S_START); + } + + NEXT(S_IGNORE); + } + } + } + +ret: + trailer_arr->_trailer_block = trailer; + trailer_arr->trailers = arr.ptr; + trailer_arr->count = arr.size; + + return rc; +} + +void git_message_trailer_array_free(git_message_trailer_array *arr) +{ + git__free(arr->_trailer_block); + git__free(arr->trailers); +} diff --git a/src/transaction.c b/src/transaction.c index 3d3f35a03..675023afd 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -5,7 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "transaction.h" + #include "repository.h" #include "strmap.h" #include "refdb.h" diff --git a/src/transport.c b/src/transport.c index b66165332..f1e18b323 100644 --- a/src/transport.c +++ b/src/transport.c @@ -4,7 +4,9 @@ * 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 "common.h" + #include "git2/types.h" #include "git2/remote.h" #include "git2/net.h" diff --git a/src/transports/auth.c b/src/transports/auth.c index c1154db34..9597cc249 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -5,9 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "auth.h" + #include "git2.h" #include "buffer.h" -#include "auth.h" static int basic_next_token( git_buf *out, git_http_auth_context *ctx, git_cred *c) diff --git a/src/transports/auth.h b/src/transports/auth.h index 52138cf8f..3b8b29eb9 100644 --- a/src/transports/auth.h +++ b/src/transports/auth.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_http_auth_h__ -#define INCLUDE_http_auth_h__ +#ifndef INCLUDE_transports_auth_h__ +#define INCLUDE_transports_auth_h__ + +#include "common.h" #include "git2.h" #include "netops.h" @@ -60,4 +62,3 @@ int git_http_auth_basic( const gitno_connection_data *connection_data); #endif - diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c index 7c868c9fd..c9bc3043d 100644 --- a/src/transports/auth_negotiate.c +++ b/src/transports/auth_negotiate.c @@ -5,10 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "auth_negotiate.h" + #ifdef GIT_GSSAPI #include "git2.h" -#include "common.h" #include "buffer.h" #include "auth.h" diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h index d7270b7ab..15a528aaf 100644 --- a/src/transports/auth_negotiate.h +++ b/src/transports/auth_negotiate.h @@ -5,9 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_auth_negotiate_h__ -#define INCLUDE_auth_negotiate_h__ +#ifndef INCLUDE_transports_auth_negotiate_h__ +#define INCLUDE_transports_auth_negotiate_h__ +#include "common.h" #include "git2.h" #include "auth.h" @@ -24,4 +25,3 @@ extern int git_http_auth_negotiate( #endif /* GIT_GSSAPI */ #endif - diff --git a/src/transports/cred.c b/src/transports/cred.c index 8e3f64435..8055e2d65 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "cred.h" + #include "git2.h" #include "smart.h" #include "git2/cred_helpers.h" diff --git a/src/transports/cred.h b/src/transports/cred.h index 2de8deee8..ed5821c55 100644 --- a/src/transports/cred.h +++ b/src/transports/cred.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_cred_h__ -#define INCLUDE_git_cred_h__ +#ifndef INCLUDE_transports_cred_h__ +#define INCLUDE_transports_cred_h__ + +#include "common.h" #include "git2/transport.h" diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c index 5cc9b0869..fdc56b17e 100644 --- a/src/transports/cred_helpers.c +++ b/src/transports/cred_helpers.c @@ -6,6 +6,7 @@ */ #include "common.h" + #include "git2/cred_helpers.h" int git_cred_userpass( diff --git a/src/transports/git.c b/src/transports/git.c index 01edfdc49..94178f2c5 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -5,12 +5,14 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include "git2.h" #include "buffer.h" #include "netops.h" #include "git2/sys/transport.h" #include "stream.h" -#include "socket_stream.h" +#include "streams/socket.h" #define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) diff --git a/src/transports/http.c b/src/transports/http.c index cb4a6d0d5..e051c8a35 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,6 +4,9 @@ * 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 "common.h" + #ifndef GIT_WINHTTP #include "git2.h" @@ -14,10 +17,11 @@ #include "remote.h" #include "smart.h" #include "auth.h" +#include "http.h" #include "auth_negotiate.h" -#include "tls_stream.h" -#include "socket_stream.h" -#include "curl_stream.h" +#include "streams/tls.h" +#include "streams/socket.h" +#include "streams/curl.h" git_http_auth_scheme auth_schemes[] = { { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, @@ -187,16 +191,6 @@ static int apply_credentials(git_buf *buf, http_subtransport *t) return context->next_token(buf, context, cred); } -static const char *user_agent(void) -{ - const char *custom = git_libgit2__user_agent(); - - if (custom) - return custom; - - return "libgit2 " LIBGIT2_VERSION; -} - static int gen_request( git_buf *buf, http_stream *s, @@ -208,7 +202,9 @@ static int gen_request( git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); - git_buf_printf(buf, "User-Agent: git/2.0 (%s)\r\n", user_agent()); + git_buf_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_buf_puts(buf, "\r\n"); git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host); if (s->chunked || content_length > 0) { diff --git a/src/transports/http.h b/src/transports/http.h new file mode 100644 index 000000000..6c4ecc9a4 --- /dev/null +++ b/src/transports/http.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_http_h__ +#define INCLUDE_transports_http_h__ + +#include "buffer.h" + +GIT_INLINE(int) git_http__user_agent(git_buf *buf) +{ + const char *ua = git_libgit2__user_agent(); + + if (!ua) + ua = "libgit2 " LIBGIT2_VERSION; + + return git_buf_printf(buf, "git/2.0 (%s)", ua); +} + +#endif diff --git a/src/transports/local.c b/src/transports/local.c index e24e99860..740cf36a9 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -4,7 +4,9 @@ * 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 "common.h" + #include "git2/types.h" #include "git2/net.h" #include "git2/repository.h" @@ -16,6 +18,7 @@ #include "git2/pack.h" #include "git2/commit.h" #include "git2/revparse.h" + #include "pack-objects.h" #include "refs.h" #include "posix.h" @@ -504,6 +507,23 @@ static int local_counting(int stage, unsigned int current, unsigned int total, v return error; } +static int foreach_reference_cb(git_reference *reference, void *payload) +{ + git_revwalk *walk = (git_revwalk *)payload; + + int error = git_revwalk_hide(walk, git_reference_target(reference)); + /* The reference is in the local repository, so the target may not + * exist on the remote. It also may not be a commit. */ + if (error == GIT_ENOTFOUND || error == GITERR_INVALID) { + giterr_clear(); + error = 0; + } + + git_reference_free(reference); + + return error; +} + static int local_download_pack( git_transport *transport, git_repository *repo, @@ -543,11 +563,6 @@ static int local_download_pack( if (git_object_type(obj) == GIT_OBJ_COMMIT) { /* Revwalker includes only wanted commits */ error = git_revwalk_push(walk, &rhead->oid); - if (!error && !git_oid_iszero(&rhead->loid)) { - error = git_revwalk_hide(walk, &rhead->loid); - if (error == GIT_ENOTFOUND) - error = 0; - } } else { /* Tag or some other wanted object. Add it on its own */ error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); @@ -557,6 +572,9 @@ static int local_download_pack( goto cleanup; } + if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) + goto cleanup; + if ((error = git_packbuilder_insert_walk(pack, walk))) goto cleanup; diff --git a/src/transports/smart.c b/src/transports/smart.c index a96fdf6fb..6d5d95fbf 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -4,8 +4,10 @@ * 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 "git2.h" + #include "smart.h" + +#include "git2.h" #include "refs.h" #include "refspec.h" #include "proxy.h" diff --git a/src/transports/smart.h b/src/transports/smart.h index b47001fe0..e33a25402 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -4,6 +4,11 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#ifndef INCLUDE_transports_smart_h__ +#define INCLUDE_transports_smart_h__ + +#include "common.h" + #include "git2.h" #include "vector.h" #include "netops.h" @@ -191,3 +196,5 @@ int git_pkt_buffer_done(git_buf *buf); int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf); int git_pkt_buffer_have(git_oid *oid, git_buf *buf); void git_pkt_free(git_pkt *pkt); + +#endif diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 8146fa163..aecfece0a 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -4,6 +4,9 @@ * 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 "common.h" + #include "git2.h" #include "git2/odb_backend.h" @@ -270,7 +273,7 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo) git_revwalk *walk = NULL; git_strarray refs; unsigned int i; - git_reference *ref; + git_reference *ref = NULL; int error; if ((error = git_reference_list(&refs, repo)) < 0) @@ -282,6 +285,9 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo) git_revwalk_sorting(walk, GIT_SORT_TIME); for (i = 0; i < refs.count; ++i) { + git_reference_free(ref); + ref = NULL; + /* No tags */ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) continue; @@ -294,16 +300,13 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo) if ((error = git_revwalk_push(walk, git_reference_target(ref))) < 0) goto on_error; - - git_reference_free(ref); } - git_strarray_free(&refs); *out = walk; - return 0; on_error: - git_revwalk_free(walk); + if (error) + git_revwalk_free(walk); git_reference_free(ref); git_strarray_free(&refs); return error; diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 4c55e3f2a..dcd9b5e27 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "ssh.h" + #ifdef GIT_SSH #include #endif @@ -15,8 +17,7 @@ #include "netops.h" #include "smart.h" #include "cred.h" -#include "socket_stream.h" -#include "ssh.h" +#include "streams/socket.h" #ifdef GIT_SSH @@ -63,7 +64,7 @@ static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) */ static int gen_proto(git_buf *request, const char *cmd, const char *url) { - char *repo; + const char *repo; int len; size_t i; @@ -91,8 +92,10 @@ done: len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; git_buf_grow(request, len); - git_buf_printf(request, "%s '%s'", cmd, repo); - git_buf_putc(request, '\0'); + git_buf_puts(request, cmd); + git_buf_puts(request, " '"); + git_buf_decode_percent(request, repo, strlen(repo)); + git_buf_puts(request, "'"); if (git_buf_oom(request)) return -1; @@ -418,8 +421,10 @@ static int _git_ssh_authenticate_session( } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) - return GIT_EAUTH; + if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || + rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || + rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) + return GIT_EAUTH; if (rc != LIBSSH2_ERROR_NONE) { if (!giterr_last()) diff --git a/src/transports/ssh.h b/src/transports/ssh.h index 2db2cc5df..d3e741f1d 100644 --- a/src/transports/ssh.h +++ b/src/transports/ssh.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_ssh_h__ -#define INCLUDE_ssh_h__ +#ifndef INCLUDE_transports_ssh_h__ +#define INCLUDE_transports_ssh_h__ + +#include "common.h" int git_transport_ssh_global_init(void); diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index fb504c912..e52d54b6d 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #ifdef GIT_WINHTTP #include "git2.h" @@ -16,6 +18,7 @@ #include "remote.h" #include "repository.h" #include "global.h" +#include "http.h" #include #include @@ -37,6 +40,14 @@ #define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 #endif +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS_1_1 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS_1_2 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 +#endif + static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; @@ -170,9 +181,15 @@ static int apply_default_credentials(HINTERNET request, int mechanisms) * is "medium" which applies to the intranet and sounds like it would correspond * to Internet Explorer security zones, but in fact does not. */ DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + DWORD native_scheme = 0; - if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) == 0 && - (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) == 0) { + if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) + native_scheme |= WINHTTP_AUTH_SCHEME_NTLM; + + if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) + native_scheme |= WINHTTP_AUTH_SCHEME_NEGOTIATE; + + if (!native_scheme) { giterr_set(GITERR_NET, "invalid authentication scheme"); return -1; } @@ -180,6 +197,9 @@ static int apply_default_credentials(HINTERNET request, int mechanisms) if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD))) return -1; + if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER, native_scheme, NULL, NULL, NULL)) + return -1; + return 0; } @@ -267,7 +287,7 @@ static int certificate_check(winhttp_stream *s, int valid) cert.parent.cert_type = GIT_CERT_X509; cert.data = cert_ctx->pbCertEncoded; cert.len = cert_ctx->cbCertEncoded; - error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->cred_acquire_payload); + error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload); CertFreeCertificateContext(cert_ctx); if (error < 0 && !giterr_last()) @@ -604,12 +624,12 @@ static int parse_unauthorized_response( if (WINHTTP_AUTH_SCHEME_NTLM & supported) { *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; *allowed_types |= GIT_CREDTYPE_DEFAULT; - *allowed_mechanisms = GIT_WINHTTP_AUTH_NEGOTIATE; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM; } if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) { *allowed_types |= GIT_CREDTYPE_DEFAULT; - *allowed_mechanisms = GIT_WINHTTP_AUTH_NEGOTIATE; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; } if (WINHTTP_AUTH_SCHEME_BASIC & supported) { @@ -690,21 +710,6 @@ static int winhttp_close_connection(winhttp_subtransport *t) return ret; } -static int user_agent(git_buf *ua) -{ - const char *custom = git_libgit2__user_agent(); - - git_buf_clear(ua); - git_buf_PUTS(ua, "git/1.0 ("); - - if (custom) - git_buf_puts(ua, custom); - else - git_buf_PUTS(ua, "libgit2 " LIBGIT2_VERSION); - - return git_buf_putc(ua, ')'); -} - static void CALLBACK winhttp_status( HINTERNET connection, DWORD_PTR ctx, @@ -747,6 +752,10 @@ static int winhttp_connect( int error = -1; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + DWORD protocols = + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; t->session = NULL; t->connection = NULL; @@ -761,7 +770,8 @@ static int winhttp_connect( return -1; } - if ((error = user_agent(&ua)) < 0) { + + if ((error = git_http__user_agent(&ua)) < 0) { git__free(wide_host); return error; } @@ -788,6 +798,16 @@ static int winhttp_connect( goto on_error; } + /* + * Do a best-effort attempt to enable TLS 1.2 but allow this to + * fail; if TLS 1.2 support is not available for some reason, + * ignore the failure (it will keep the default protocols). + */ + WinHttpSetOption(t->session, + WINHTTP_OPTION_SECURE_PROTOCOLS, + &protocols, + sizeof(protocols)); + if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { giterr_set(GITERR_OS, "failed to set timeouts for WinHTTP"); goto on_error; diff --git a/src/tree-cache.c b/src/tree-cache.c index 548054136..b331d22a2 100644 --- a/src/tree-cache.c +++ b/src/tree-cache.c @@ -6,6 +6,7 @@ */ #include "tree-cache.h" + #include "pool.h" #include "tree.h" diff --git a/src/tree-cache.h b/src/tree-cache.h index c44ca7cf5..e02300e6e 100644 --- a/src/tree-cache.h +++ b/src/tree-cache.h @@ -9,6 +9,7 @@ #define INCLUDE_tree_cache_h__ #include "common.h" + #include "pool.h" #include "buffer.h" #include "git2/oid.h" diff --git a/src/tree.c b/src/tree.c index 6b1d1b238..fdf36f850 100644 --- a/src/tree.c +++ b/src/tree.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "commit.h" #include "tree.h" + +#include "commit.h" #include "git2/repository.h" #include "git2/object.h" #include "fileops.h" @@ -440,16 +440,16 @@ int git_tree__parse(void *_tree, git_odb_object *odb_obj) unsigned int attr; if (parse_mode(&attr, buffer, &buffer) < 0 || !buffer) - return tree_error("Failed to parse tree. Can't parse filemode", NULL); + return tree_error("failed to parse tree: can't parse filemode", NULL); if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) - return tree_error("Failed to parse tree. Object is corrupted", NULL); + return tree_error("failed to parse tree: object is corrupted", NULL); if ((filename_len = nul - buffer) == 0) - return tree_error("Failed to parse tree. Can't parse filename", NULL); + return tree_error("failed to parse tree: can't parse filename", NULL); if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ) - return tree_error("Failed to parse tree. Can't parse OID", NULL); + return tree_error("failed to parse tree: can't parse OID", NULL); /* Allocate the entry */ { @@ -496,7 +496,10 @@ static int append_entry( int error = 0; if (!valid_entry_name(bld->repo, filename)) - return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); + return tree_error("failed to insert entry: invalid name for a tree entry", filename); + + if (git_oid_iszero(id)) + return tree_error("failed to insert entry: invalid null OID for a tree entry", filename); entry = alloc_entry(filename, strlen(filename), id); GITERR_CHECK_ALLOC(entry); @@ -735,14 +738,17 @@ int git_treebuilder_insert( assert(bld && id && filename); if (!valid_filemode(filemode)) - return tree_error("Failed to insert entry. Invalid filemode for file", filename); + return tree_error("failed to insert entry: invalid filemode for file", filename); if (!valid_entry_name(bld->repo, filename)) - return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); + return tree_error("failed to insert entry: invalid name for a tree entry", filename); + + if (git_oid_iszero(id)) + return tree_error("failed to insert entry: invalid null OID", filename); if (filemode != GIT_FILEMODE_COMMIT && !git_object__is_valid(bld->repo, id, otype_from_mode(filemode))) - return tree_error("Failed to insert entry; invalid object specified", filename); + return tree_error("failed to insert entry: invalid object specified", filename); pos = git_strmap_lookup_index(bld->map, filename); if (git_strmap_valid_index(bld->map, pos)) { @@ -793,7 +799,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) git_tree_entry *entry = treebuilder_get(bld, filename); if (entry == NULL) - return tree_error("Failed to remove entry. File isn't in the tree", filename); + return tree_error("failed to remove entry: file isn't in the tree", filename); git_strmap_delete(bld->map, filename); git_tree_entry_free(entry); @@ -946,12 +952,12 @@ int git_tree_entry_bypath( return GIT_ENOTFOUND; } - /* If there's only a slash left in the path, we + /* If there's only a slash left in the path, we * return the current entry; otherwise, we keep * walking down the path */ if (path[filename_len + 1] != '\0') break; - + /* fall through */ case '\0': /* If there are no more components in the path, return * this entry */ diff --git a/src/tree.h b/src/tree.h index 5e7a66e04..00f4b06eb 100644 --- a/src/tree.h +++ b/src/tree.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_tree_h__ #define INCLUDE_tree_h__ +#include "common.h" + #include "git2/tree.h" #include "repository.h" #include "odb.h" diff --git a/src/tsort.c b/src/tsort.c index e59819204..8d1ed9787 100644 --- a/src/tsort.c +++ b/src/tsort.c @@ -310,7 +310,6 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, #define PUSH_NEXT() do {\ len = count_run(dst, curr, size, store);\ run = minrun;\ - if (run < minrun) run = minrun;\ if (run > (ssize_t)size - curr) run = size - curr;\ if (run > len) {\ bisort(&dst[curr], len, run, cmp, payload);\ diff --git a/src/unix/map.c b/src/unix/map.c index 9d9b1fe38..8a07fcff9 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -4,7 +4,10 @@ * 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 "common.h" + +#include "git2/common.h" #if !defined(GIT_WIN32) && !defined(NO_MMAP) diff --git a/src/unix/posix.h b/src/unix/posix.h index 52985fd8a..f2fffd5c9 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -4,8 +4,12 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_posix__unix_h__ -#define INCLUDE_posix__unix_h__ +#ifndef INCLUDE_unix_posix_h__ +#define INCLUDE_unix_posix_h__ + +#ifndef LIBGIT2_NO_FEATURES_H +# include "git2/sys/features.h" +#endif #include #include @@ -71,7 +75,7 @@ GIT_INLINE(int) p_fsync(int fd) #define p_timeval timeval -#ifdef HAVE_FUTIMENS +#ifdef GIT_USE_FUTIMENS GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) { struct timespec s[2]; @@ -85,7 +89,7 @@ GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) # define p_futimes futimes #endif -#ifdef HAVE_REGCOMP_L +#ifdef GIT_USE_REGCOMP_L #include GIT_INLINE(int) p_regcomp(regex_t *preg, const char *pattern, int cflags) { diff --git a/src/unix/pthread.h b/src/unix/pthread.h index 3f23d10d5..233561b4e 100644 --- a/src/unix/pthread.h +++ b/src/unix/pthread.h @@ -53,4 +53,4 @@ typedef struct { #define git_rwlock_free(a) pthread_rwlock_destroy(a) #define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER -#endif /* INCLUDE_unix_pthread_h__ */ +#endif diff --git a/src/unix/realpath.c b/src/unix/realpath.c index 2e49150c2..893bac87b 100644 --- a/src/unix/realpath.c +++ b/src/unix/realpath.c @@ -4,7 +4,10 @@ * 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 "common.h" + +#include "git2/common.h" #ifndef GIT_WIN32 diff --git a/src/util.c b/src/util.c index a44f4c9ac..34841df4f 100644 --- a/src/util.c +++ b/src/util.c @@ -4,11 +4,10 @@ * 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 "util.h" + #include "common.h" -#include -#include -#include "posix.h" #ifdef GIT_WIN32 # include "win32/w32_buffer.h" @@ -250,29 +249,21 @@ void git__strtolower(char *str) git__strntolower(str, strlen(str)); } -int git__prefixcmp(const char *str, const char *prefix) -{ - for (;;) { - unsigned char p = *(prefix++), s; - if (!p) - return 0; - if ((s = *(str++)) != p) - return s - p; - } -} - -int git__prefixcmp_icase(const char *str, const char *prefix) -{ - return strncasecmp(str, prefix, strlen(prefix)); -} - -int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) { int s, p; - while(str_n--) { - s = (unsigned char)git__tolower(*str++); - p = (unsigned char)git__tolower(*prefix++); + while (str_n--) { + s = (unsigned char)*str++; + p = (unsigned char)*prefix++; + + if (icase) { + s = git__tolower(s); + p = git__tolower(p); + } + + if (!p) + return 0; if (s != p) return s - p; @@ -281,6 +272,26 @@ int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) return (0 - *prefix); } +int git__prefixcmp(const char *str, const char *prefix) +{ + return prefixcmp(str, SIZE_MAX, prefix, false); +} + +int git__prefixncmp(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, false); +} + +int git__prefixcmp_icase(const char *str, const char *prefix) +{ + return prefixcmp(str, SIZE_MAX, prefix, true); +} + +int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, true); +} + int git__suffixcmp(const char *str, const char *suffix) { size_t a = strlen(str); @@ -464,9 +475,11 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) switch(len & 3) { case 3: k1 ^= tail[2] << 16; + /* fall through */ case 2: k1 ^= tail[1] << 8; + /* fall through */ case 1: k1 ^= tail[0]; - MURMUR_BLOCK(); + MURMUR_BLOCK(); } h1 ^= len; diff --git a/src/util.h b/src/util.h index eb15250d8..f6d19cfde 100644 --- a/src/util.h +++ b/src/util.h @@ -7,10 +7,42 @@ #ifndef INCLUDE_util_h__ #define INCLUDE_util_h__ +#include "common.h" + +#ifndef GIT_WIN32 +# include +#endif + #include "git2/buffer.h" + #include "buffer.h" +#include "common.h" +#include "strnlen.h" +#include "thread-utils.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define GIT_DATE_RFC2822_SZ 32 + +/** + * Return the length of a constant string. + * We are aware that `strlen` performs the same task and is usually + * optimized away by the compiler, whilst being safer because it returns + * valid values when passed a pointer instead of a constant string; however + * this macro will transparently work with wide-char and single-char strings. + */ +#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) #if defined(GIT_MSVC_CRTDBG) + /* Enable MSVC CRTDBG memory leak reporting. * * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC @@ -36,110 +68,8 @@ * Finally, CRTDBG must be explicitly enabled and configured at program * startup. See tests/main.c for an example. */ -#include -#include + #include "win32/w32_crtdbg_stacktrace.h" -#endif - -#include "common.h" -#include "strnlen.h" - -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) -#define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) -#ifndef min -# define min(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef max -# define max(a,b) ((a) > (b) ? (a) : (b)) -#endif - -#define GIT_DATE_RFC2822_SZ 32 - -/** - * Return the length of a constant string. - * We are aware that `strlen` performs the same task and is usually - * optimized away by the compiler, whilst being safer because it returns - * valid values when passed a pointer instead of a constant string; however - * this macro will transparently work with wide-char and single-char strings. - */ -#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) - -#if defined(GIT_MSVC_CRTDBG) - -GIT_INLINE(void *) git__crtdbg__malloc(size_t len, const char *file, int line) -{ - void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) giterr_set_oom(); - return ptr; -} - -GIT_INLINE(void *) git__crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) giterr_set_oom(); - return ptr; -} - -GIT_INLINE(char *) git__crtdbg__strdup(const char *str, const char *file, int line) -{ - char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) giterr_set_oom(); - return ptr; -} - -GIT_INLINE(char *) git__crtdbg__strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = git__crtdbg__malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -GIT_INLINE(char *) git__crtdbg__substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = git__crtdbg__malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!new_ptr) giterr_set_oom(); - return new_ptr; -} - -GIT_INLINE(void *) git__crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ? - NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); -} - -GIT_INLINE(void *) git__crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return git__crtdbg__reallocarray(NULL, nelem, elsize, file, line); -} #define git__malloc(len) git__crtdbg__malloc(len, __FILE__, __LINE__) #define git__calloc(nelem, elsize) git__crtdbg__calloc(nelem, elsize, __FILE__, __LINE__) @@ -254,6 +184,7 @@ GIT_INLINE(void) git__free(void *ptr) extern int git__prefixcmp(const char *str, const char *prefix); extern int git__prefixcmp_icase(const char *str, const char *prefix); +extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); @@ -363,8 +294,6 @@ extern int git__strncasecmp(const char *a, const char *b, size_t sz); extern int git__strcasesort_cmp(const char *a, const char *b); -#include "thread-utils.h" - typedef struct { git_atomic refcount; void *owner; @@ -373,22 +302,22 @@ typedef struct { typedef void (*git_refcount_freeptr)(void *r); #define GIT_REFCOUNT_INC(r) { \ - git_atomic_inc(&((git_refcount *)(r))->refcount); \ + git_atomic_inc(&(r)->rc.refcount); \ } #define GIT_REFCOUNT_DEC(_r, do_free) { \ - git_refcount *r = (git_refcount *)(_r); \ + git_refcount *r = &(_r)->rc; \ int val = git_atomic_dec(&r->refcount); \ if (val <= 0 && r->owner == NULL) { do_free(_r); } \ } #define GIT_REFCOUNT_OWN(r, o) { \ - ((git_refcount *)(r))->owner = o; \ + (r)->rc.owner = o; \ } -#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner) +#define GIT_REFCOUNT_OWNER(r) ((r)->rc.owner) -#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount) +#define GIT_REFCOUNT_VAL(r) git_atomic_get((r)->rc.refcount) static signed char from_hex[] = { @@ -614,4 +543,4 @@ GIT_INLINE(double) git__timer(void) extern int git__getenv(git_buf *out, const char *name); -#endif /* INCLUDE_util_h__ */ +#endif diff --git a/src/varint.c b/src/varint.c index beac8c709..9ffc1d744 100644 --- a/src/varint.c +++ b/src/varint.c @@ -5,7 +5,6 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "varint.h" uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) diff --git a/src/varint.h b/src/varint.h index 650ec7d2a..652e22486 100644 --- a/src/varint.h +++ b/src/varint.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_varint_h__ #define INCLUDE_varint_h__ +#include "common.h" + #include extern int git_encode_varint(unsigned char *, size_t, uintmax_t); diff --git a/src/vector.c b/src/vector.c index 620a1f56c..b12fa942d 100644 --- a/src/vector.c +++ b/src/vector.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "vector.h" + #include "integer.h" /* In elements, not bytes */ diff --git a/src/win32/dir.c b/src/win32/dir.c index 8a724a4e9..1d37874e4 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -4,6 +4,9 @@ * 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 "dir.h" + #define GIT__WIN32_NO_WRAP_DIR #include "posix.h" diff --git a/src/win32/dir.h b/src/win32/dir.h index bef39d774..acd64729e 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -4,10 +4,11 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_dir_h__ -#define INCLUDE_dir_h__ +#ifndef INCLUDE_win32_dir_h__ +#define INCLUDE_win32_dir_h__ #include "common.h" + #include "w32_util.h" struct git__dirent { @@ -40,4 +41,4 @@ extern int git__closedir(git__DIR *); # define closedir git__closedir # endif -#endif /* INCLUDE_dir_h__ */ +#endif diff --git a/src/win32/error.c b/src/win32/error.c index 6b450093f..3a52fb5a9 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "error.h" + #include "utf-conv.h" #ifdef GIT_WINHTTP diff --git a/src/win32/error.h b/src/win32/error.h index 12947a2e6..9e81141ce 100644 --- a/src/win32/error.h +++ b/src/win32/error.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_win32_error_h__ -#define INCLUDE_git_win32_error_h__ +#ifndef INCLUDE_win32_error_h__ +#define INCLUDE_win32_error_h__ + +#include "common.h" extern char *git_win32_get_error_message(DWORD error_code); diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 1c768f7f4..d56aa1fd2 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -5,10 +5,11 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "findfile.h" + #include "path_w32.h" #include "utf-conv.h" #include "path.h" -#include "findfile.h" #define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" diff --git a/src/win32/findfile.h b/src/win32/findfile.h index 3d5fff439..e7bcf948a 100644 --- a/src/win32/findfile.h +++ b/src/win32/findfile.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_findfile_h__ -#define INCLUDE_git_findfile_h__ +#ifndef INCLUDE_win32_findfile_h__ +#define INCLUDE_win32_findfile_h__ + +#include "common.h" extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath); extern int git_win32__find_global_dirs(git_buf *out); diff --git a/src/win32/map.c b/src/win32/map.c index 5fcc1085b..6a17aeb64 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -5,6 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + #include "map.h" #include diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 698ebed1a..aa2bef98d 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_mingw_compat__ -#define INCLUDE_mingw_compat__ +#ifndef INCLUDE_win32_mingw_compat_h__ +#define INCLUDE_win32_mingw_compat_h__ #if defined(__MINGW32__) @@ -20,4 +20,4 @@ void __mingworg_MemoryBarrier(void); #endif -#endif /* INCLUDE_mingw_compat__ */ +#endif diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h index 12b50d981..ea77820a2 100644 --- a/src/win32/msvc-compat.h +++ b/src/win32/msvc-compat.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_msvc_compat__ -#define INCLUDE_msvc_compat__ +#ifndef INCLUDE_win32_msvc_compat_h__ +#define INCLUDE_win32_msvc_compat_h__ #if defined(_MSC_VER) @@ -19,4 +19,4 @@ typedef SSIZE_T ssize_t; #define GIT_STDLIB_CALL __cdecl -#endif /* INCLUDE_msvc_compat__ */ +#endif diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index 40b95c33b..5e24260f7 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -5,9 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "path.h" #include "path_w32.h" + +#include "path.h" #include "utf-conv.h" #include "posix.h" #include "reparse.h" @@ -18,11 +18,6 @@ #define PATH__ABSOLUTE_LEN 3 -#define path__is_dirsep(p) ((p) == '/' || (p) == '\\') - -#define path__is_absolute(p) \ - (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) - #define path__is_nt_namespace(p) \ (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) @@ -59,7 +54,7 @@ static wchar_t *path__skip_server(wchar_t *path) wchar_t *c; for (c = path; *c; c++) { - if (path__is_dirsep(*c)) + if (git_path_is_dirsep(*c)) return c + 1; } @@ -73,9 +68,9 @@ static wchar_t *path__skip_prefix(wchar_t *path) if (wcsncmp(path, L"UNC\\", 4) == 0) path = path__skip_server(path + 4); - else if (path__is_absolute(path)) + else if (git_path_is_absolute(path)) path += PATH__ABSOLUTE_LEN; - } else if (path__is_absolute(path)) { + } else if (git_path_is_absolute(path)) { path += PATH__ABSOLUTE_LEN; } else if (path__is_unc(path)) { path = path__skip_server(path + 2); @@ -196,7 +191,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) dest += PATH__NT_NAMESPACE_LEN; /* See if this is an absolute path (beginning with a drive letter) */ - if (path__is_absolute(src)) { + if (git_path_is_absolute(src)) { if (git__utf8_to_16(dest, MAX_PATH, src) < 0) goto on_error; } @@ -220,7 +215,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) if (path__cwd(dest, MAX_PATH) < 0) goto on_error; - if (!path__is_absolute(dest)) { + if (!git_path_is_absolute(dest)) { errno = ENOENT; goto on_error; } diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h index 3d9f82860..83ffd1f6f 100644 --- a/src/win32/path_w32.h +++ b/src/win32/path_w32.h @@ -4,10 +4,11 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_path_w32_h__ -#define INCLUDE_git_path_w32_h__ +#ifndef INCLUDE_win32_path_w32_h__ +#define INCLUDE_win32_path_w32_h__ #include "common.h" + #include "vector.h" /* diff --git a/src/win32/posix.h b/src/win32/posix.h index 64769ecd3..d5ab2e8f5 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_posix__w32_h__ -#define INCLUDE_posix__w32_h__ +#ifndef INCLUDE_win32_posix_h__ +#define INCLUDE_win32_posix_h__ #include "common.h" #include "../posix.h" diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index e4fe4142c..f51e1e546 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -4,6 +4,9 @@ * 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 "common.h" + #include "../posix.h" #include "../fileops.h" #include "path.h" @@ -162,12 +165,15 @@ GIT_INLINE(bool) last_error_retryable(void) #define do_with_retries(fn, remediation) \ do { \ - int __tries, __ret; \ - for (__tries = 0; __tries < git_win32__retries; __tries++) { \ - if (__tries && (__ret = (remediation)) != 0) \ - return __ret; \ + int __retry, __ret; \ + for (__retry = git_win32__retries; __retry; __retry--) { \ if ((__ret = (fn)) != GIT_RETRY) \ return __ret; \ + if (__retry > 1 && (__ret = (remediation)) != 0) { \ + if (__ret == GIT_RETRY) \ + continue; \ + return __ret; \ + } \ Sleep(5); \ } \ return -1; \ @@ -186,7 +192,7 @@ static int ensure_writable(wchar_t *path) if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) goto on_error; - return 0; + return GIT_RETRY; on_error: set_errno(); diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h index 10ca0b80c..851a083d5 100644 --- a/src/win32/precompiled.h +++ b/src/win32/precompiled.h @@ -1,3 +1,5 @@ +#include "common.h" + #include #include #include @@ -20,4 +22,3 @@ #endif #include "git2.h" -#include "common.h" diff --git a/src/win32/reparse.h b/src/win32/reparse.h index 70f9fd652..5f7408a1b 100644 --- a/src/win32/reparse.h +++ b/src/win32/reparse.h @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_win32_reparse_h__ -#define INCLUDE_git_win32_reparse_h__ +#ifndef INCLUDE_win32_reparse_h__ +#define INCLUDE_win32_reparse_h__ /* This structure is defined on MSDN at * http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx diff --git a/src/win32/thread.c b/src/win32/thread.c index 87318c9d3..2d9600515 100644 --- a/src/win32/thread.c +++ b/src/win32/thread.c @@ -6,6 +6,7 @@ */ #include "thread.h" + #include "../global.h" #define CLEAN_THREAD_EXIT 0x6F012842 diff --git a/src/win32/thread.h b/src/win32/thread.h index 7f4a2170f..41cbf015b 100644 --- a/src/win32/thread.h +++ b/src/win32/thread.h @@ -8,7 +8,7 @@ #ifndef INCLUDE_win32_thread_h__ #define INCLUDE_win32_thread_h__ -#include "../common.h" +#include "common.h" #if defined (_MSC_VER) # define GIT_RESTRICT __restrict @@ -61,4 +61,4 @@ int git_rwlock_wrlock(git_rwlock *); int git_rwlock_wrunlock(git_rwlock *); int git_rwlock_free(git_rwlock *); -#endif /* INCLUDE_win32_thread_h__ */ +#endif diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 96fd4606e..4bde3023a 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -5,7 +5,6 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "utf-conv.h" GIT_INLINE(void) git__set_errno(void) diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 33b95f59f..6090a4b35 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -4,11 +4,12 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_utfconv_h__ -#define INCLUDE_git_utfconv_h__ +#ifndef INCLUDE_win32_utf_conv_h__ +#define INCLUDE_win32_utf_conv_h__ + +#include "common.h" #include -#include "common.h" #ifndef WC_ERR_INVALID_CHARS # define WC_ERR_INVALID_CHARS 0x80 diff --git a/src/win32/w32_buffer.c b/src/win32/w32_buffer.c index 9122baaa6..45c024d31 100644 --- a/src/win32/w32_buffer.c +++ b/src/win32/w32_buffer.c @@ -5,8 +5,8 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" #include "w32_buffer.h" + #include "../buffer.h" #include "utf-conv.h" diff --git a/src/win32/w32_buffer.h b/src/win32/w32_buffer.h index 62243986f..43298e4a7 100644 --- a/src/win32/w32_buffer.h +++ b/src/win32/w32_buffer.h @@ -4,8 +4,10 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_git_win32_buffer_h__ -#define INCLUDE_git_win32_buffer_h__ +#ifndef INCLUDE_win32_w32_buffer_h__ +#define INCLUDE_win32_w32_buffer_h__ + +#include "common.h" #include "../buffer.h" diff --git a/src/win32/w32_crtdbg_stacktrace.c b/src/win32/w32_crtdbg_stacktrace.c index 2dbdaf45b..7b3c3fb4c 100644 --- a/src/win32/w32_crtdbg_stacktrace.c +++ b/src/win32/w32_crtdbg_stacktrace.c @@ -5,9 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "w32_crtdbg_stacktrace.h" + #if defined(GIT_MSVC_CRTDBG) #include "w32_stack.h" -#include "w32_crtdbg_stacktrace.h" #define CRTDBG_STACKTRACE__UID_LEN (15) diff --git a/src/win32/w32_crtdbg_stacktrace.h b/src/win32/w32_crtdbg_stacktrace.h index 40ca60d53..3f580357e 100644 --- a/src/win32/w32_crtdbg_stacktrace.h +++ b/src/win32/w32_crtdbg_stacktrace.h @@ -4,11 +4,19 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_w32_crtdbg_stacktrace_h__ -#define INCLUDE_w32_crtdbg_stacktrace_h__ +#ifndef INCLUDE_win32_w32_crtdbg_stacktrace_h__ +#define INCLUDE_win32_w32_crtdbg_stacktrace_h__ + +#include "common.h" #if defined(GIT_MSVC_CRTDBG) +#include +#include + +#include "git2/errors.h" +#include "strnlen.h" + /** * Initialize our memory leak tracking and de-dup data structures. * This should ONLY be called by git_libgit2_init(). @@ -89,5 +97,80 @@ GIT_EXTERN(int) git_win32__crtdbg_stacktrace__dump( */ const char *git_win32__crtdbg_stacktrace(int skip, const char *file); +GIT_INLINE(void *) git__crtdbg__malloc(size_t len, const char *file, int line) +{ + void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) giterr_set_oom(); + return ptr; +} + +GIT_INLINE(void *) git__crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) giterr_set_oom(); + return ptr; +} + +GIT_INLINE(char *) git__crtdbg__strdup(const char *str, const char *file, int line) +{ + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) giterr_set_oom(); + return ptr; +} + +GIT_INLINE(char *) git__crtdbg__strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = git__crtdbg__malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +GIT_INLINE(char *) git__crtdbg__substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = git__crtdbg__malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!new_ptr) giterr_set_oom(); + return new_ptr; +} + +GIT_INLINE(void *) git__crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ? + NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); +} + +GIT_INLINE(void *) git__crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return git__crtdbg__reallocarray(NULL, nelem, elsize, file, line); +} + + #endif #endif diff --git a/src/win32/w32_stack.c b/src/win32/w32_stack.c index 15af3dcb7..b40f9d2b4 100644 --- a/src/win32/w32_stack.c +++ b/src/win32/w32_stack.c @@ -5,11 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "w32_stack.h" + #if defined(GIT_MSVC_CRTDBG) #include "Windows.h" #include "Dbghelp.h" #include "win32/posix.h" -#include "w32_stack.h" #include "hash.h" /** diff --git a/src/win32/w32_stack.h b/src/win32/w32_stack.h index 21170bd2f..5f0009e0b 100644 --- a/src/win32/w32_stack.h +++ b/src/win32/w32_stack.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_w32_stack_h__ -#define INCLUDE_w32_stack_h__ +#ifndef INCLUDE_win32_w32_stack_h__ +#define INCLUDE_win32_w32_stack_h__ + +#include "common.h" #if defined(GIT_MSVC_CRTDBG) @@ -135,4 +137,4 @@ int git_win32__stack( const char *prefix, const char *suffix); #endif /* GIT_MSVC_CRTDBG */ -#endif /* INCLUDE_w32_stack_h__ */ +#endif diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 77973b502..6531f47a7 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -5,8 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_w32_util_h__ -#define INCLUDE_w32_util_h__ +#ifndef INCLUDE_win32_w32_util_h__ +#define INCLUDE_win32_w32_util_h__ + +#include "common.h" #include "utf-conv.h" #include "posix.h" diff --git a/src/win32/win32-compat.h b/src/win32/win32-compat.h index f888fd69e..dee40a438 100644 --- a/src/win32/win32-compat.h +++ b/src/win32/win32-compat.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_win32_compat__ -#define INCLUDE_win32_compat__ +#ifndef INCLUDE_win32_win32_compat_h__ +#define INCLUDE_win32_win32_compat_h__ #include #include @@ -49,4 +49,4 @@ struct p_stat { #define stat p_stat -#endif /* INCLUDE_win32_compat__ */ +#endif diff --git a/src/worktree.c b/src/worktree.c index ede155b69..4b18db7d6 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -5,14 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" +#include "worktree.h" #include "git2/branch.h" #include "git2/commit.h" #include "git2/worktree.h" #include "repository.h" -#include "worktree.h" static bool is_worktree_dir(const char *dir) { @@ -384,7 +383,7 @@ out: return err; } -int git_worktree_lock(git_worktree *wt, char *creason) +int git_worktree_lock(git_worktree *wt, const char *reason) { git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; int err; @@ -397,8 +396,8 @@ int git_worktree_lock(git_worktree *wt, char *creason) if ((err = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) goto out; - if (creason) - git_buf_attach_notowned(&buf, creason, strlen(creason)); + if (reason) + git_buf_attach_notowned(&buf, reason, strlen(reason)); if ((err = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) goto out; diff --git a/src/worktree.h b/src/worktree.h index 57c2e65f0..52d13cced 100644 --- a/src/worktree.h +++ b/src/worktree.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_worktree_h__ #define INCLUDE_worktree_h__ +#include "common.h" + #include "git2/common.h" #include "git2/worktree.h" diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h index f08f72e16..5b13e77a0 100644 --- a/src/xdiff/xdiff.h +++ b/src/xdiff/xdiff.h @@ -13,15 +13,13 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * */ -#include "../util.h" - #if !defined(XDIFF_H) #define XDIFF_H @@ -29,22 +27,29 @@ extern "C" { #endif /* #ifdef __cplusplus */ +/* xpparm_t.flags */ +#define XDF_NEED_MINIMAL (1 << 0) -#define XDF_NEED_MINIMAL (1 << 1) -#define XDF_IGNORE_WHITESPACE (1 << 2) -#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) -#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) -#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) - -#define XDF_PATIENCE_DIFF (1 << 5) -#define XDF_HISTOGRAM_DIFF (1 << 6) -#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) -#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) +#define XDF_IGNORE_WHITESPACE (1 << 1) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) +#define XDF_IGNORE_CR_AT_EOL (1 << 4) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ + XDF_IGNORE_WHITESPACE_CHANGE | \ + XDF_IGNORE_WHITESPACE_AT_EOL | \ + XDF_IGNORE_CR_AT_EOL) #define XDF_IGNORE_BLANK_LINES (1 << 7) +#define XDF_PATIENCE_DIFF (1 << 14) +#define XDF_HISTOGRAM_DIFF (1 << 15) +#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) +#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) + +#define XDF_INDENT_HEURISTIC (1 << 23) + +/* xdemitconf_t.flags */ #define XDL_EMIT_FUNCNAMES (1 << 0) -#define XDL_EMIT_COMMON (1 << 1) #define XDL_EMIT_FUNCCONTEXT (1 << 2) #define XDL_MMB_READONLY (1 << 0) @@ -81,6 +86,10 @@ typedef struct s_mmbuffer { typedef struct s_xpparam { unsigned long flags; + + /* See Documentation/diff-options.txt. */ + char **anchors; + size_t anchors_nr; } xpparam_t; typedef struct s_xdemitcb { diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c index f4d01b48c..3a71ef678 100644 --- a/src/xdiff/xdiffi.c +++ b/src/xdiff/xdiffi.c @@ -13,15 +13,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * */ #include "xinclude.h" -#include "common.h" #include "integer.h" @@ -31,7 +30,12 @@ #define XDL_SNAKE_CNT 20 #define XDL_K_HEUR 4 - +/** Declare a function as always inlined. */ +#if defined(_MSC_VER) +# define XDL_INLINE(type) static __inline type +#else +# define XDL_INLINE(type) static inline type +#endif typedef struct s_xdpsplit { long i1, i2; @@ -404,106 +408,544 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { - long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; - char *rchg = xdf->rchg, *rchgo = xdfo->rchg; - xrecord_t **recs = xdf->recs; +static int recs_match(xrecord_t *rec1, xrecord_t *rec2, long flags) +{ + return (rec1->ha == rec2->ha && + xdl_recmatch(rec1->ptr, rec1->size, + rec2->ptr, rec2->size, + flags)); +} + +/* + * If a line is indented more than this, get_indent() just returns this value. + * This avoids having to do absurd amounts of work for data that are not + * human-readable text, and also ensures that the output of get_indent fits within + * an int. + */ +#define MAX_INDENT 200 + +/* + * Return the amount of indentation of the specified line, treating TAB as 8 + * columns. Return -1 if line is empty or contains only whitespace. Clamp the + * output value at MAX_INDENT. + */ +static int get_indent(xrecord_t *rec) +{ + long i; + int ret = 0; + + for (i = 0; i < rec->size; i++) { + char c = rec->ptr[i]; + + if (!XDL_ISSPACE(c)) + return ret; + else if (c == ' ') + ret += 1; + else if (c == '\t') + ret += 8 - ret % 8; + /* ignore other whitespace characters */ + + if (ret >= MAX_INDENT) + return MAX_INDENT; + } + + /* The line contains only whitespace. */ + return -1; +} + +/* + * If more than this number of consecutive blank rows are found, just return this + * value. This avoids requiring O(N^2) work for pathological cases, and also + * ensures that the output of score_split fits in an int. + */ +#define MAX_BLANKS 20 + +/* Characteristics measured about a hypothetical split position. */ +struct split_measurement { + /* + * Is the split at the end of the file (aside from any blank lines)? + */ + int end_of_file; /* - * This is the same of what GNU diff does. Move back and forward - * change groups for a consistent and pretty diff output. This also - * helps in finding joinable change groups and reduce the diff size. + * How much is the line immediately following the split indented (or -1 if + * the line is blank): */ - for (ix = ixo = 0;;) { - /* - * Find the first changed line in the to-be-compacted file. - * We need to keep track of both indexes, so if we find a - * changed lines group on the other file, while scanning the - * to-be-compacted file, we need to skip it properly. Note - * that loops that are testing for changed lines on rchg* do - * not need index bounding since the array is prepared with - * a zero at position -1 and N. - */ - for (; ix < nrec && !rchg[ix]; ix++) - while (rchgo[ixo++]); - if (ix == nrec) + int indent; + + /* + * How many consecutive lines above the split are blank? + */ + int pre_blank; + + /* + * How much is the nearest non-blank line above the split indented (or -1 + * if there is no such line)? + */ + int pre_indent; + + /* + * How many lines after the line following the split are blank? + */ + int post_blank; + + /* + * How much is the nearest non-blank line after the line following the + * split indented (or -1 if there is no such line)? + */ + int post_indent; +}; + +struct split_score { + /* The effective indent of this split (smaller is preferred). */ + int effective_indent; + + /* Penalty for this split (smaller is preferred). */ + int penalty; +}; + +/* + * Fill m with information about a hypothetical split of xdf above line split. + */ +static void measure_split(const xdfile_t *xdf, long split, + struct split_measurement *m) +{ + long i; + + if (split >= xdf->nrec) { + m->end_of_file = 1; + m->indent = -1; + } else { + m->end_of_file = 0; + m->indent = get_indent(xdf->recs[split]); + } + + m->pre_blank = 0; + m->pre_indent = -1; + for (i = split - 1; i >= 0; i--) { + m->pre_indent = get_indent(xdf->recs[i]); + if (m->pre_indent != -1) + break; + m->pre_blank += 1; + if (m->pre_blank == MAX_BLANKS) { + m->pre_indent = 0; break; - - /* - * Record the start of a changed-group in the to-be-compacted file - * and find the end of it, on both to-be-compacted and other file - * indexes (ix and ixo). - */ - ixs = ix; - for (ix++; rchg[ix]; ix++); - for (; rchgo[ixo]; ixo++); - - do { - grpsiz = ix - ixs; - - /* - * If the line before the current change group, is equal to - * the last line of the current change group, shift backward - * the group. - */ - while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha && - xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) { - rchg[--ixs] = 1; - rchg[--ix] = 0; - - /* - * This change might have joined two change groups, - * so we try to take this scenario in account by moving - * the start index accordingly (and so the other-file - * end-of-group index). - */ - for (; rchg[ixs - 1]; ixs--); - while (rchgo[--ixo]); - } - - /* - * Record the end-of-group position in case we are matched - * with a group of changes in the other file (that is, the - * change record before the end-of-group index in the other - * file is set). - */ - ixref = rchgo[ixo - 1] ? ix: nrec; - - /* - * If the first line of the current change group, is equal to - * the line next of the current change group, shift forward - * the group. - */ - while (ix < nrec && recs[ixs]->ha == recs[ix]->ha && - xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) { - rchg[ixs++] = 0; - rchg[ix++] = 1; - - /* - * This change might have joined two change groups, - * so we try to take this scenario in account by moving - * the start index accordingly (and so the other-file - * end-of-group index). Keep tracking the reference - * index in case we are shifting together with a - * corresponding group of changes in the other file. - */ - for (; rchg[ix]; ix++); - while (rchgo[++ixo]) - ixref = ix; - } - } while (grpsiz != ix - ixs); - - /* - * Try to move back the possibly merged group of changes, to match - * the recorded position in the other file. - */ - while (ixref < ix) { - rchg[--ixs] = 1; - rchg[--ix] = 0; - while (rchgo[--ixo]); } } + m->post_blank = 0; + m->post_indent = -1; + for (i = split + 1; i < xdf->nrec; i++) { + m->post_indent = get_indent(xdf->recs[i]); + if (m->post_indent != -1) + break; + m->post_blank += 1; + if (m->post_blank == MAX_BLANKS) { + m->post_indent = 0; + break; + } + } +} + +/* + * The empirically-determined weight factors used by score_split() below. + * Larger values means that the position is a less favorable place to split. + * + * Note that scores are only ever compared against each other, so multiplying + * all of these weight/penalty values by the same factor wouldn't change the + * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. + * In practice, these numbers are chosen to be large enough that they can be + * adjusted relative to each other with sufficient precision despite using + * integer math. + */ + +/* Penalty if there are no non-blank lines before the split */ +#define START_OF_FILE_PENALTY 1 + +/* Penalty if there are no non-blank lines after the split */ +#define END_OF_FILE_PENALTY 21 + +/* Multiplier for the number of blank lines around the split */ +#define TOTAL_BLANK_WEIGHT (-30) + +/* Multiplier for the number of blank lines after the split */ +#define POST_BLANK_WEIGHT 6 + +/* + * Penalties applied if the line is indented more than its predecessor + */ +#define RELATIVE_INDENT_PENALTY (-4) +#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 + +/* + * Penalties applied if the line is indented less than both its predecessor and + * its successor + */ +#define RELATIVE_OUTDENT_PENALTY 24 +#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 + +/* + * Penalties applied if the line is indented less than its predecessor but not + * less than its successor + */ +#define RELATIVE_DEDENT_PENALTY 23 +#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 + +/* + * We only consider whether the sum of the effective indents for splits are + * less than (-1), equal to (0), or greater than (+1) each other. The resulting + * value is multiplied by the following weight and combined with the penalty to + * determine the better of two scores. + */ +#define INDENT_WEIGHT 60 + +/* + * Compute a badness score for the hypothetical split whose measurements are + * stored in m. The weight factors were determined empirically using the tools and + * corpus described in + * + * https://github.com/mhagger/diff-slider-tools + * + * Also see that project if you want to improve the weights based on, for example, + * a larger or more diverse corpus. + */ +static void score_add_split(const struct split_measurement *m, struct split_score *s) +{ + /* + * A place to accumulate penalty factors (positive makes this index more + * favored): + */ + int post_blank, total_blank, indent, any_blanks; + + if (m->pre_indent == -1 && m->pre_blank == 0) + s->penalty += START_OF_FILE_PENALTY; + + if (m->end_of_file) + s->penalty += END_OF_FILE_PENALTY; + + /* + * Set post_blank to the number of blank lines following the split, + * including the line immediately after the split: + */ + post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; + total_blank = m->pre_blank + post_blank; + + /* Penalties based on nearby blank lines: */ + s->penalty += TOTAL_BLANK_WEIGHT * total_blank; + s->penalty += POST_BLANK_WEIGHT * post_blank; + + if (m->indent != -1) + indent = m->indent; + else + indent = m->post_indent; + + any_blanks = (total_blank != 0); + + /* Note that the effective indent is -1 at the end of the file: */ + s->effective_indent += indent; + + if (indent == -1) { + /* No additional adjustments needed. */ + } else if (m->pre_indent == -1) { + /* No additional adjustments needed. */ + } else if (indent > m->pre_indent) { + /* + * The line is indented more than its predecessor. + */ + s->penalty += any_blanks ? + RELATIVE_INDENT_WITH_BLANK_PENALTY : + RELATIVE_INDENT_PENALTY; + } else if (indent == m->pre_indent) { + /* + * The line has the same indentation level as its predecessor. + * No additional adjustments needed. + */ + } else { + /* + * The line is indented less than its predecessor. It could be + * the block terminator of the previous block, but it could + * also be the start of a new block (e.g., an "else" block, or + * maybe the previous block didn't have a block terminator). + * Try to distinguish those cases based on what comes next: + */ + if (m->post_indent != -1 && m->post_indent > indent) { + /* + * The following line is indented more. So it is likely + * that this line is the start of a block. + */ + s->penalty += any_blanks ? + RELATIVE_OUTDENT_WITH_BLANK_PENALTY : + RELATIVE_OUTDENT_PENALTY; + } else { + /* + * That was probably the end of a block. + */ + s->penalty += any_blanks ? + RELATIVE_DEDENT_WITH_BLANK_PENALTY : + RELATIVE_DEDENT_PENALTY; + } + } +} + +static int score_cmp(struct split_score *s1, struct split_score *s2) +{ + /* -1 if s1.effective_indent < s2->effective_indent, etc. */ + int cmp_indents = ((s1->effective_indent > s2->effective_indent) - + (s1->effective_indent < s2->effective_indent)); + + return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); +} + +/* + * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group + * of lines that was inserted or deleted from the corresponding version of the + * file). We consider there to be such a group at the beginning of the file, at + * the end of the file, and between any two unchanged lines, though most such + * groups will usually be empty. + * + * If the first line in a group is equal to the line following the group, then + * the group can be slid down. Similarly, if the last line in a group is equal + * to the line preceding the group, then the group can be slid up. See + * group_slide_down() and group_slide_up(). + * + * Note that loops that are testing for changed lines in xdf->rchg do not need + * index bounding since the array is prepared with a zero at position -1 and N. + */ +struct xdlgroup { + /* + * The index of the first changed line in the group, or the index of + * the unchanged line above which the (empty) group is located. + */ + long start; + + /* + * The index of the first unchanged line after the group. For an empty + * group, end is equal to start. + */ + long end; +}; + +/* + * Initialize g to point at the first group in xdf. + */ +static void group_init(xdfile_t *xdf, struct xdlgroup *g) +{ + g->start = g->end = 0; + while (xdf->rchg[g->end]) + g->end++; +} + +/* + * Move g to describe the next (possibly empty) group in xdf and return 0. If g + * is already at the end of the file, do nothing and return -1. + */ +XDL_INLINE(int) group_next(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->end == xdf->nrec) + return -1; + + g->start = g->end + 1; + for (g->end = g->start; xdf->rchg[g->end]; g->end++) + ; + + return 0; +} + +/* + * Move g to describe the previous (possibly empty) group in xdf and return 0. + * If g is already at the beginning of the file, do nothing and return -1. + */ +XDL_INLINE(int) group_previous(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->start == 0) + return -1; + + g->end = g->start - 1; + for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) + ; + + return 0; +} + +/* + * If g can be slid toward the end of the file, do so, and if it bumps into a + * following group, expand this group to include it. Return 0 on success or -1 + * if g cannot be slid down. + */ +static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g, long flags) +{ + if (g->end < xdf->nrec && + recs_match(xdf->recs[g->start], xdf->recs[g->end], flags)) { + xdf->rchg[g->start++] = 0; + xdf->rchg[g->end++] = 1; + + while (xdf->rchg[g->end]) + g->end++; + + return 0; + } else { + return -1; + } +} + +/* + * If g can be slid toward the beginning of the file, do so, and if it bumps + * into a previous group, expand this group to include it. Return 0 on success + * or -1 if g cannot be slid up. + */ +static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g, long flags) +{ + if (g->start > 0 && + recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1], flags)) { + xdf->rchg[--g->start] = 1; + xdf->rchg[--g->end] = 0; + + while (xdf->rchg[g->start - 1]) + g->start--; + + return 0; + } else { + return -1; + } +} + +static void xdl_bug(const char *msg) +{ + fprintf(stderr, "BUG: %s\n", msg); + exit(1); +} + +/* + * Move back and forward change groups for a consistent and pretty diff output. + * This also helps in finding joinable change groups and reducing the diff + * size. + */ +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + struct xdlgroup g, go; + long earliest_end, end_matching_other; + long groupsize; + + group_init(xdf, &g); + group_init(xdfo, &go); + + while (1) { + /* If the group is empty in the to-be-compacted file, skip it: */ + if (g.end == g.start) + goto next; + + /* + * Now shift the change up and then down as far as possible in + * each direction. If it bumps into any other changes, merge them. + */ + do { + groupsize = g.end - g.start; + + /* + * Keep track of the last "end" index that causes this + * group to align with a group of changed lines in the + * other file. -1 indicates that we haven't found such + * a match yet: + */ + end_matching_other = -1; + + /* Shift the group backward as much as possible: */ + while (!group_slide_up(xdf, &g, flags)) + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding up"); + + /* + * This is this highest that this group can be shifted. + * Record its end index: + */ + earliest_end = g.end; + + if (go.end > go.start) + end_matching_other = g.end; + + /* Now shift the group forward as far as possible: */ + while (1) { + if (group_slide_down(xdf, &g, flags)) + break; + if (group_next(xdfo, &go)) + xdl_bug("group sync broken sliding down"); + + if (go.end > go.start) + end_matching_other = g.end; + } + } while (groupsize != g.end - g.start); + + /* + * If the group can be shifted, then we can possibly use this + * freedom to produce a more intuitive diff. + * + * The group is currently shifted as far down as possible, so the + * heuristics below only have to handle upwards shifts. + */ + + if (g.end == earliest_end) { + /* no shifting was possible */ + } else if (end_matching_other != -1) { + /* + * Move the possibly merged group of changes back to line + * up with the last group of changes from the other file + * that it can align with. + */ + while (go.end == go.start) { + if (group_slide_up(xdf, &g, flags)) + xdl_bug("match disappeared"); + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding to match"); + } + } else if (flags & XDF_INDENT_HEURISTIC) { + /* + * Indent heuristic: a group of pure add/delete lines + * implies two splits, one between the end of the "before" + * context and the start of the group, and another between + * the end of the group and the beginning of the "after" + * context. Some splits are aesthetically better and some + * are worse. We compute a badness "score" for each split, + * and add the scores for the two splits to define a + * "score" for each position that the group can be shifted + * to. Then we pick the shift with the lowest score. + */ + long shift, best_shift = -1; + struct split_score best_score; + + for (shift = earliest_end; shift <= g.end; shift++) { + struct split_measurement m; + struct split_score score = {0, 0}; + + measure_split(xdf, shift, &m); + score_add_split(&m, &score); + measure_split(xdf, shift - groupsize, &m); + score_add_split(&m, &score); + if (best_shift == -1 || + score_cmp(&score, &best_score) <= 0) { + best_score.effective_indent = score.effective_indent; + best_score.penalty = score.penalty; + best_shift = shift; + } + } + + while (g.end > best_shift) { + if (group_slide_up(xdf, &g, flags)) + xdl_bug("best shift unreached"); + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding to blank line"); + } + } + + next: + /* Move past the just-processed group: */ + if (group_next(xdf, &g)) + break; + if (group_next(xdfo, &go)) + xdl_bug("group sync broken moving to next group"); + } + + if (!group_next(xdfo, &go)) + xdl_bug("group sync broken at end of file"); + return 0; } diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h index 8b81206c9..8f1c7c8b0 100644 --- a/src/xdiff/xdiffi.h +++ b/src/xdiff/xdiffi.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c index 600fd1fdd..0ffa6553a 100644 --- a/src/xdiff/xemit.c +++ b/src/xdiff/xemit.c @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * @@ -22,15 +22,6 @@ #include "xinclude.h" - - - -static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec); -static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb); - - - - static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { *rec = xdf->recs[ri]->ptr; @@ -110,7 +101,7 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) if (len > 0 && (isalpha((unsigned char)*rec) || /* identifier? */ - *rec == '_' || /* also identifier? */ + *rec == '_' || /* also identifier? */ *rec == '$')) { /* identifiers from VMS and other esoterico */ if (len > sz) len = sz; @@ -122,22 +113,20 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) return -1; } -static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg) { - xdfile_t *xdf = &xe->xdf2; - const char *rchg = xdf->rchg; - long ix; +static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, + char *buf, long sz) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + if (!xecfg->find_func) + return def_ff(rec, len, buf, sz, xecfg->find_func_priv); + return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); +} - (void)xscr; - (void)xecfg; - - for (ix = 0; ix < xdf->nrec; ix++) { - if (rchg[ix]) - continue; - if (xdl_emit_record(xdf, ix, "", ecb)) - return -1; - } - return 0; +static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) +{ + char dummy[1]; + return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; } struct func_line { @@ -148,7 +137,6 @@ struct func_line { static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, struct func_line *func_line, long start, long limit) { - find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; long l, size, step = (start > limit) ? -1 : 1; char *buf, dummy[1]; @@ -156,9 +144,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, size = func_line ? sizeof(func_line->buf) : sizeof(dummy); for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { - const char *rec; - long reclen = xdl_get_rec(&xe->xdf1, l, &rec); - long len = ff(rec, reclen, buf, size, xecfg->find_func_priv); + long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); if (len >= 0) { if (func_line) func_line->len = len; @@ -168,6 +154,18 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, return -1; } +static int is_empty_rec(xdfile_t *xdf, long ri) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + + while (len > 0 && XDL_ISSPACE(*rec)) { + rec++; + len--; + } + return !len; +} + int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg) { long s1, s2, e1, e2, lctx; @@ -175,9 +173,6 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, long funclineprev = -1; struct func_line func_line = { 0 }; - if (xecfg->flags & XDL_EMIT_COMMON) - return xdl_emit_common(xe, xscr, ecb, xecfg); - for (xch = xscr; xch; xch = xche->next) { xche = xdl_get_hunk(&xch, xecfg); if (!xch) @@ -187,7 +182,33 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { - long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1); + long fs1, i1 = xch->i1; + + /* Appended chunk? */ + if (i1 >= xe->xdf1.nrec) { + long i2 = xch->i2; + + /* + * We don't need additional context if + * a whole function was added. + */ + while (i2 < xe->xdf2.nrec) { + if (is_func_rec(&xe->xdf2, xecfg, i2)) + goto post_context_calculation; + i2++; + } + + /* + * Otherwise get more context from the + * pre-image. + */ + i1 = xe->xdf1.nrec - 1; + } + + fs1 = get_func_line(xe, xecfg, NULL, i1, -1); + while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && + !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) + fs1--; if (fs1 < 0) fs1 = 0; if (fs1 < s1) { @@ -196,7 +217,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, } } - again: + post_context_calculation: lctx = xecfg->ctxlen; lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); @@ -208,6 +229,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, long fe1 = get_func_line(xe, xecfg, NULL, xche->i1 + xche->chg1, xe->xdf1.nrec); + while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) + fe1--; if (fe1 < 0) fe1 = xe->xdf1.nrec; if (fe1 > e1) { @@ -221,11 +244,12 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, * its new end. */ if (xche->next) { - long l = xche->next->i1; - if (l <= e1 || + long l = XDL_MIN(xche->next->i1, + xe->xdf1.nrec - 1); + if (l - xecfg->ctxlen <= e1 || get_func_line(xe, xecfg, NULL, l, e1) < 0) { xche = xche->next; - goto again; + goto post_context_calculation; } } } diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h index d29710770..1b9887e67 100644 --- a/src/xdiff/xemit.h +++ b/src/xdiff/xemit.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c index 0c2edb89c..ca8605e31 100644 --- a/src/xdiff/xhistogram.c +++ b/src/xdiff/xhistogram.c @@ -44,7 +44,6 @@ #include "xinclude.h" #include "xtypes.h" #include "xdiff.h" -#include "common.h" #define MAX_PTR UINT_MAX #define MAX_CNT UINT_MAX diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h index 4a1cde909..068ce42f8 100644 --- a/src/xdiff/xinclude.h +++ b/src/xdiff/xinclude.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * @@ -42,5 +42,6 @@ #include "xdiffi.h" #include "xemit.h" +#include "common.h" #endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h index 165a895a9..2809a28ca 100644 --- a/src/xdiff/xmacros.h +++ b/src/xdiff/xmacros.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c index 6448b5542..6fec95aa3 100644 --- a/src/xdiff/xmerge.c +++ b/src/xdiff/xmerge.c @@ -13,15 +13,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * */ #include "xinclude.h" -#include "common.h" typedef struct s_xdmerge { struct s_xdmerge *next; @@ -110,7 +109,7 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, return 0; } -static int xdl_recs_copy_0(size_t *out, int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy_0(size_t *out, int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { xrecord_t **recs; size_t size = 0; @@ -132,6 +131,12 @@ static int xdl_recs_copy_0(size_t *out, int use_orig, xdfenv_t *xe, int i, int c if (add_nl) { i = recs[count - 1]->size; if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (needs_cr) { + if (dest) + dest[size] = '\r'; + GITERR_CHECK_ALLOC_ADD(&size, size, 1); + } + if (dest) dest[size] = '\n'; @@ -143,14 +148,58 @@ static int xdl_recs_copy_0(size_t *out, int use_orig, xdfenv_t *xe, int i, int c return 0; } -static int xdl_recs_copy(size_t *out, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy(size_t *out, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { - return xdl_recs_copy_0(out, 0, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(out, 0, xe, i, count, needs_cr, add_nl, dest); } -static int xdl_orig_copy(size_t *out, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_orig_copy(size_t *out, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { - return xdl_recs_copy_0(out, 1, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(out, 1, xe, i, count, needs_cr, add_nl, dest); +} + +/* + * Returns 1 if the i'th line ends in CR/LF (if it is the last line and + * has no eol, the preceding line, if any), 0 if it ends in LF-only, and + * -1 if the line ending cannot be determined. + */ +static int is_eol_crlf(xdfile_t *file, int i) +{ + long size; + + if (i < file->nrec - 1) + /* All lines before the last *must* end in LF */ + return (size = file->recs[i]->size) > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!file->nrec) + /* Cannot determine eol style from empty file */ + return -1; + if ((size = file->recs[i]->size) && + file->recs[i]->ptr[size - 1] == '\n') + /* Last line; ends in LF; Is it CR/LF? */ + return size > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!i) + /* The only line has no eol */ + return -1; + /* Determine eol from second-to-last line */ + return (size = file->recs[i - 1]->size) > 1 && + file->recs[i - 1]->ptr[size - 2] == '\r'; +} + +static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) +{ + int needs_cr; + + /* Match post-images' preceding, or first, lines' end-of-line style */ + needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); + if (needs_cr) + needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); + /* Look at pre-image's first line, unless we already settled on LF */ + if (needs_cr) + needs_cr = is_eol_crlf(&xe1->xdf1, 0); + /* If still undecided, use LF-only */ + return needs_cr < 0 ? 0 : needs_cr; } static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, @@ -162,6 +211,7 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, int marker1_size = (name1 ? (int)strlen(name1) + 1 : 0); int marker2_size = (name2 ? (int)strlen(name2) + 1 : 0); int marker3_size = (name3 ? (int)strlen(name3) + 1 : 0); + int needs_cr = is_cr_needed(xe1, xe2, m); size_t copied; *out = 0; @@ -170,14 +220,14 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, marker_size = DEFAULT_CONFLICT_MARKER_SIZE; /* Before conflicting part */ - if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, + if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, 0, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); if (!dest) { - GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker1_size); + GITERR_CHECK_ALLOC_ADD5(&size, size, marker_size, 1, needs_cr, marker1_size); } else { memset(dest + size, '<', marker_size); size += marker_size; @@ -186,11 +236,13 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name1, marker1_size - 1); size += marker1_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } /* Postimage from side #1 */ - if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, 1, + if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, needs_cr, 1, dest ? dest + size : NULL) < 0) return -1; @@ -199,7 +251,7 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, if (style == XDL_MERGE_DIFF3) { /* Shared preimage */ if (!dest) { - GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker3_size); + GITERR_CHECK_ALLOC_ADD5(&size, size, marker_size, 1, needs_cr, marker3_size); } else { memset(dest + size, '|', marker_size); size += marker_size; @@ -208,32 +260,36 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name3, marker3_size - 1); size += marker3_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } - if (xdl_orig_copy(&copied, xe1, m->i0, m->chg0, 1, + if (xdl_orig_copy(&copied, xe1, m->i0, m->chg0, needs_cr, 1, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); } if (!dest) { - GITERR_CHECK_ALLOC_ADD3(&size, size, marker_size, 1); + GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, needs_cr); } else { memset(dest + size, '=', marker_size); size += marker_size; + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } /* Postimage from side #2 */ - if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 1, + if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, needs_cr, 1, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); if (!dest) { - GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker2_size); + GITERR_CHECK_ALLOC_ADD5(&size, size, marker_size, 1, needs_cr, marker2_size); } else { memset(dest + size, '>', marker_size); size += marker_size; @@ -242,6 +298,8 @@ static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name2, marker2_size - 1); size += marker2_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } @@ -275,14 +333,16 @@ static int xdl_fill_merge_buffer(size_t *out, } else if (m->mode & 3) { /* Before conflicting part */ - if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, + if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, 0, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); /* Postimage from side #1 */ if (m->mode & 1) { - if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, (m->mode & 2), + int needs_cr = is_cr_needed(xe1, xe2, m); + + if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); @@ -290,7 +350,7 @@ static int xdl_fill_merge_buffer(size_t *out, /* Postimage from side #2 */ if (m->mode & 2) { - if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 0, + if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 0, 0, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); @@ -300,7 +360,7 @@ static int xdl_fill_merge_buffer(size_t *out, i = m->i1 + m->chg1; } - if (xdl_recs_copy(&copied, xe1, i, xe1->xdf2.nrec - i, 0, + if (xdl_recs_copy(&copied, xe1, i, xe1->xdf2.nrec - i, 0, 0, dest ? dest + size : NULL) < 0) return -1; GITERR_CHECK_ALLOC_ADD(&size, size, copied); diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c index 04e1a1ab2..cedf39cc3 100644 --- a/src/xdiff/xpatience.c +++ b/src/xdiff/xpatience.c @@ -1,6 +1,6 @@ /* * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * @@ -62,6 +62,12 @@ struct hashmap { * initially, "next" reflects only the order in file1. */ struct entry *next, *previous; + + /* + * If 1, this entry can serve as an anchor. See + * Documentation/diff-options.txt for more information. + */ + unsigned anchor : 1; } *entries, *first, *last; /* were common records found? */ unsigned long has_matches; @@ -70,8 +76,19 @@ struct hashmap { xpparam_t const *xpp; }; +static int is_anchor(xpparam_t const *xpp, const char *line) +{ + unsigned long i; + for (i = 0; i < xpp->anchors_nr; i++) { + if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) + return 1; + } + return 0; +} + /* The argument "pass" is 1 for the first file, 2 for the second. */ -static void insert_record(int line, struct hashmap *map, int pass) +static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, + int pass) { xrecord_t **records = pass == 1 ? map->env->xdf1.recs : map->env->xdf2.recs; @@ -110,6 +127,7 @@ static void insert_record(int line, struct hashmap *map, int pass) return; map->entries[index].line1 = line; map->entries[index].hash = record->ha; + map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); if (!map->first) map->first = map->entries + index; if (map->last) { @@ -147,11 +165,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, /* First, fill with entries from the first file */ while (count1--) - insert_record(line1++, result, 1); + insert_record(xpp, line1++, result, 1); /* Then search for matches in the second file */ while (count2--) - insert_record(line2++, result, 2); + insert_record(xpp, line2++, result, 2); return 0; } @@ -166,7 +184,7 @@ static int binary_search(struct entry **sequence, int longest, int left = -1, right = longest; while (left + 1 < right) { - int middle = (left + right) / 2; + int middle = left + (right - left) / 2; /* by construction, no two entries can be equal */ if (sequence[middle]->line2 > entry->line2) right = middle; @@ -192,14 +210,28 @@ static struct entry *find_longest_common_sequence(struct hashmap *map) int longest = 0, i; struct entry *entry; + /* + * If not -1, this entry in sequence must never be overridden. + * Therefore, overriding entries before this has no effect, so + * do not do that either. + */ + int anchor_i = -1; + for (entry = map->first; entry; entry = entry->next) { if (!entry->line2 || entry->line2 == NON_UNIQUE) continue; i = binary_search(sequence, longest, entry); entry->previous = i < 0 ? NULL : sequence[i]; - sequence[++i] = entry; - if (i == longest) + ++i; + if (i <= anchor_i) + continue; + sequence[i] = entry; + if (entry->anchor) { + anchor_i = i; + longest = anchor_i + 1; + } else if (i == longest) { longest++; + } } /* No common unique lines were found */ diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c index 13b55aba7..abeb8fb84 100644 --- a/src/xdiff/xprepare.c +++ b/src/xdiff/xprepare.c @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h index 8fb06a537..947d9fc1b 100644 --- a/src/xdiff/xprepare.h +++ b/src/xdiff/xprepare.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h index 2511aef8d..8442bd436 100644 --- a/src/xdiff/xtypes.h +++ b/src/xdiff/xtypes.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c index 30f2a30ac..17c9ae184 100644 --- a/src/xdiff/xutils.c +++ b/src/xdiff/xutils.c @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * @@ -62,14 +62,14 @@ int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, void *xdl_mmfile_first(mmfile_t *mmf, long *size) { - *size = (long)mmf->size; + *size = mmf->size; return mmf->ptr; } long xdl_mmfile_size(mmfile_t *mmf) { - return (long)mmf->size; + return mmf->size; } @@ -154,6 +154,24 @@ int xdl_blankline(const char *line, long size, long flags) return (i == size); } +/* + * Have we eaten everything on the line, except for an optional + * CR at the very end? + */ +static int ends_with_optional_cr(const char *l, long s, long i) +{ + int complete = s && l[s-1] == '\n'; + + if (complete) + s--; + if (s == i) + return 1; + /* do not ignore CR at the end of an incomplete line */ + if (complete && s == i + 1 && l[i] == '\r') + return 1; + return 0; +} + int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) { int i1, i2; @@ -168,7 +186,8 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) /* * -w matches everything that matches with -b, and -b in turn - * matches everything that matches with --ignore-space-at-eol. + * matches everything that matches with --ignore-space-at-eol, + * which in turn matches everything that matches with --ignore-cr-at-eol. * * Each flavor of ignoring needs different logic to skip whitespaces * while we have both sides to compare. @@ -198,8 +217,18 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) return 0; } } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { - while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) - ; /* keep going */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + } else if (flags & XDF_IGNORE_CR_AT_EOL) { + /* Find the first difference and see how the line ends */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + return (ends_with_optional_cr(l1, s1, i1) && + ends_with_optional_cr(l2, s2, i2)); } /* @@ -226,9 +255,16 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; + int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; for (; ptr < top && *ptr != '\n'; ptr++) { - if (XDL_ISSPACE(*ptr)) { + if (cr_at_eol_only) { + /* do not ignore CR at the end of an incomplete line */ + if (*ptr == '\r' && + (ptr + 1 < top && ptr[1] == '\n')) + continue; + } + else if (XDL_ISSPACE(*ptr)) { const char *ptr2 = ptr; int at_eol; while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) @@ -260,7 +296,6 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, return ha; } - unsigned long xdl_hash_record(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; @@ -277,7 +312,6 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { return ha; } - unsigned int xdl_hashbits(unsigned int size) { unsigned int val = 1, bits = 0; @@ -305,23 +339,9 @@ int xdl_num_out(char *out, long val) { *str++ = '0'; *str = '\0'; - return (int)(str - out); + return str - out; } - -long xdl_atol(char const *str, char const **next) { - long val, base; - char const *top; - - for (top = str; XDL_ISDIGIT(*top); top++); - if (next) - *next = top; - for (val = 0, base = 1, top--; top >= str; top--, base *= 10) - val += base * (long)(*top - '0'); - return val; -} - - int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, const char *func, long funclen, xdemitcb_t *ecb) { int nb = 0; @@ -356,8 +376,8 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, nb += 3; if (func && funclen) { buf[nb++] = ' '; - if (funclen > (long)sizeof(buf) - nb - 1) - funclen = (long)sizeof(buf) - nb - 1; + if (funclen > (long)(sizeof(buf) - nb - 1)) + funclen = sizeof(buf) - nb - 1; memcpy(buf + nb, func, funclen); nb += funclen; } diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h index 8f952a8e6..fba7bae03 100644 --- a/src/xdiff/xutils.h +++ b/src/xdiff/xutils.h @@ -13,8 +13,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see + * . * * Davide Libenzi * @@ -31,15 +31,12 @@ int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, int xdl_cha_init(chastore_t *cha, long isize, long icount); void xdl_cha_free(chastore_t *cha); void *xdl_cha_alloc(chastore_t *cha); -void *xdl_cha_first(chastore_t *cha); -void *xdl_cha_next(chastore_t *cha); long xdl_guess_lines(mmfile_t *mf, long sample); int xdl_blankline(const char *line, long size, long flags); int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); unsigned long xdl_hash_record(char const **data, char const *top, long flags); unsigned int xdl_hashbits(unsigned int size); int xdl_num_out(char *out, long val); -long xdl_atol(char const *str, char const **next); int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, const char *func, long funclen, xdemitcb_t *ecb); int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, diff --git a/src/zstream.c b/src/zstream.c index 141b49b27..affa55653 100644 --- a/src/zstream.c +++ b/src/zstream.c @@ -5,25 +5,31 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "zstream.h" + #include -#include "zstream.h" #include "buffer.h" #define ZSTREAM_BUFFER_SIZE (1024 * 1024) #define ZSTREAM_BUFFER_MIN_EXTRA 8 -static int zstream_seterr(git_zstream *zs) +GIT_INLINE(int) zstream_seterr(git_zstream *zs) { - if (zs->zerr == Z_OK || zs->zerr == Z_STREAM_END) + switch (zs->zerr) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ return 0; - - if (zs->zerr == Z_MEM_ERROR) + case Z_MEM_ERROR: giterr_set_oom(); - else if (zs->z.msg) - giterr_set_str(GITERR_ZLIB, zs->z.msg); - else - giterr_set(GITERR_ZLIB, "unknown compression error"); + break; + default: + if (zs->z.msg) + giterr_set_str(GITERR_ZLIB, zs->z.msg); + else + giterr_set(GITERR_ZLIB, "unknown compression error"); + } return -1; } @@ -81,9 +87,52 @@ size_t git_zstream_suggest_output_len(git_zstream *zstream) return ZSTREAM_BUFFER_MIN_EXTRA; } +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream) +{ + size_t in_queued, in_used, out_queued; + + /* set up input data */ + zstream->z.next_in = (Bytef *)zstream->in; + + /* feed as much data to zlib as it can consume, at most UINT_MAX */ + if (zstream->in_len > UINT_MAX) { + zstream->z.avail_in = UINT_MAX; + zstream->flush = Z_NO_FLUSH; + } else { + zstream->z.avail_in = (uInt)zstream->in_len; + zstream->flush = Z_FINISH; + } + in_queued = (size_t)zstream->z.avail_in; + + /* set up output data */ + zstream->z.next_out = out; + zstream->z.avail_out = (uInt)*out_len; + + if ((size_t)zstream->z.avail_out != *out_len) + zstream->z.avail_out = UINT_MAX; + out_queued = (size_t)zstream->z.avail_out; + + /* compress next chunk */ + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflate(&zstream->z, zstream->flush); + else + zstream->zerr = deflate(&zstream->z, zstream->flush); + + if (zstream_seterr(zstream)) + return -1; + + in_used = (in_queued - zstream->z.avail_in); + zstream->in_len -= in_used; + zstream->in += in_used; + + *out_len = (out_queued - zstream->z.avail_out); + + return 0; +} + int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) { - int zflush = Z_FINISH; size_t out_remain = *out_len; if (zstream->in_len && zstream->zerr == Z_STREAM_END) { @@ -92,46 +141,17 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) } while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { - size_t out_queued, in_queued, out_used, in_used; + size_t out_written = out_remain; - /* set up in data */ - zstream->z.next_in = (Bytef *)zstream->in; - zstream->z.avail_in = (uInt)zstream->in_len; - if ((size_t)zstream->z.avail_in != zstream->in_len) { - zstream->z.avail_in = INT_MAX; - zflush = Z_NO_FLUSH; - } else { - zflush = Z_FINISH; - } - in_queued = (size_t)zstream->z.avail_in; + if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) + return -1; - /* set up out data */ - zstream->z.next_out = out; - zstream->z.avail_out = (uInt)out_remain; - if ((size_t)zstream->z.avail_out != out_remain) - zstream->z.avail_out = INT_MAX; - out_queued = (size_t)zstream->z.avail_out; - - /* compress next chunk */ - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflate(&zstream->z, zflush); - else - zstream->zerr = deflate(&zstream->z, zflush); - - if (zstream->zerr == Z_STREAM_ERROR) - return zstream_seterr(zstream); - - out_used = (out_queued - zstream->z.avail_out); - out_remain -= out_used; - out = ((char *)out) + out_used; - - in_used = (in_queued - zstream->z.avail_in); - zstream->in_len -= in_used; - zstream->in += in_used; + out_remain -= out_written; + out = ((char *)out) + out_written; } /* either we finished the input or we did not flush the data */ - assert(zstream->in_len > 0 || zflush == Z_FINISH); + assert(zstream->in_len > 0 || zstream->flush == Z_FINISH); /* set out_size to number of bytes actually written to output */ *out_len = *out_len - out_remain; diff --git a/src/zstream.h b/src/zstream.h index f0006d32e..47ecc1322 100644 --- a/src/zstream.h +++ b/src/zstream.h @@ -7,9 +7,10 @@ #ifndef INCLUDE_zstream_h__ #define INCLUDE_zstream_h__ +#include "common.h" + #include -#include "common.h" #include "buffer.h" typedef enum { @@ -22,6 +23,7 @@ typedef struct { git_zstream_t type; const char *in; size_t in_len; + int flush; int zerr; } git_zstream; @@ -34,6 +36,11 @@ int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); size_t git_zstream_suggest_output_len(git_zstream *zstream); +/* get as much output as is available in the input buffer */ +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream); + +/* get all the output from the entire input buffer */ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); bool git_zstream_done(git_zstream *zstream); @@ -43,4 +50,4 @@ void git_zstream_reset(git_zstream *zstream); int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len); int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len); -#endif /* INCLUDE_zstream_h__ */ +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..775f33f2d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,63 @@ +FIND_PACKAGE(PythonInterp) + +IF(NOT PYTHONINTERP_FOUND) + MESSAGE(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. " + "Make sure python is available, or pass -DBUILD_CLAR=OFF to skip building the tests") +ENDIF() + +SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/resources/") +SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}") +ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") +ADD_DEFINITIONS(-DCLAR_TMPDIR=\"libgit2_tests\") + +INCLUDE_DIRECTORIES(${CLAR_PATH} ${libgit2_BINARY_DIR}/src) +FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) +SET(SRC_CLAR "main.c" "clar_libgit2.c" "clar_libgit2_trace.c" "clar_libgit2_timer.c" "clar.c") + +IF(MSVC_IDE) + LIST(APPEND SRC_CLAR "precompiled.c") +ENDIF() + +ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite + COMMAND ${PYTHON_EXECUTABLE} generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . + DEPENDS ${SRC_TEST} + WORKING_DIRECTORY ${CLAR_PATH} +) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +SET_SOURCE_FILES_PROPERTIES( + ${CLAR_PATH}/clar.c + PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) + +LINK_DIRECTORIES(${LIBGIT2_LIBDIRS}) +INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES}) + +ADD_EXECUTABLE(libgit2_clar ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) + +SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) + +IF (${CMAKE_VERSION} VERSION_LESS 2.8.12) + # Already handled by a global INCLUDE_DIRECTORY() +ELSE() + TARGET_INCLUDE_DIRECTORIES(libgit2_clar PRIVATE ../src PUBLIC ../include) +ENDIF() + +TARGET_LINK_LIBRARIES(libgit2_clar ${LIBGIT2_LIBS}) +IDE_SPLIT_SOURCES(libgit2_clar) + +IF (MSVC_IDE) + # Precompiled headers + SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + SET_SOURCE_FILES_PROPERTIES("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") +ENDIF () + +IF (USE_HTTPS) + ADD_TEST(libgit2_clar "${libgit2_BINARY_DIR}/libgit2_clar" -ionline -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) +ELSE () + ADD_TEST(libgit2_clar "${libgit2_BINARY_DIR}/libgit2_clar" -v -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style) +ENDIF () + +# Add additional test targets that require special setup +ADD_TEST(libgit2_clar-proxy_credentials "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) +ADD_TEST(libgit2_clar-ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c index a089ee408..d241c03b5 100644 --- a/tests/attr/ignore.c +++ b/tests/attr/ignore.c @@ -51,6 +51,16 @@ void test_attr_ignore__allow_root(void) assert_is_ignored(false, "NewFolder/NewFolder/File.txt"); } +void test_attr_ignore__ignore_space(void) +{ + cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder \n/NewFolder/NewFolder"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(true, "NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); +} + void test_attr_ignore__ignore_root(void) { cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder"); @@ -303,3 +313,46 @@ void test_attr_ignore__test(void) assert_is_ignored(true, "dist/foo.o"); assert_is_ignored(true, "bin/foo"); } + +void test_attr_ignore__unignore_dir_succeeds(void) +{ + cl_git_rewritefile("attr/.gitignore", + "*.c\n" + "!src/*.c\n"); + assert_is_ignored(false, "src/foo.c"); + assert_is_ignored(true, "src/foo/foo.c"); +} + +void test_attr_ignore__case_insensitive_unignores_previous_rule(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", true)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(false, "case/file"); +} + +void test_attr_ignore__case_sensitive_unignore_does_nothing(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", false)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(true, "case/file"); +} diff --git a/tests/buf/percent.c b/tests/buf/percent.c new file mode 100644 index 000000000..60534a053 --- /dev/null +++ b/tests/buf/percent.c @@ -0,0 +1,49 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static void expect_decode_pass(const char *expected, const char *encoded) +{ + git_buf in = GIT_BUF_INIT, out = GIT_BUF_INIT; + + /* + * ensure that we only read the given length of the input buffer + * by putting garbage at the end. this will ensure that we do + * not, eg, rely on nul-termination or walk off the end of the buf. + */ + cl_git_pass(git_buf_puts(&in, encoded)); + cl_git_pass(git_buf_PUTS(&in, "TRAILER")); + + cl_git_pass(git_buf_decode_percent(&out, in.ptr, strlen(encoded))); + + cl_assert_equal_s(expected, git_buf_cstr(&out)); + cl_assert_equal_i(strlen(expected), git_buf_len(&out)); + + git_buf_free(&in); + git_buf_free(&out); +} + +void test_buf_percent__decode_succeeds(void) +{ + expect_decode_pass("", ""); + expect_decode_pass(" ", "%20"); + expect_decode_pass("a", "a"); + expect_decode_pass(" a", "%20a"); + expect_decode_pass("a ", "a%20"); + expect_decode_pass("github.com", "github.com"); + expect_decode_pass("github.com", "githu%62.com"); + expect_decode_pass("github.com", "github%2ecom"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar ", "foo%20bar%20"); +} + +void test_buf_percent__ignores_invalid(void) +{ + expect_decode_pass("githu%%.com", "githu%%.com"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github%2.com", "github%2.com"); + expect_decode_pass("githu%2z.com", "githu%2z.com"); + expect_decode_pass("github.co%9z", "github.co%9z"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github.co%", "github.co%"); +} diff --git a/tests/checkout/head.c b/tests/checkout/head.c index ded86df33..99061466f 100644 --- a/tests/checkout/head.c +++ b/tests/checkout/head.c @@ -136,3 +136,131 @@ void test_checkout_head__do_remove_tracked_subdir(void) cl_assert(!git_path_isfile("testrepo/subdir/tracked-file")); cl_assert(git_path_isfile("testrepo/subdir/untracked-file")); } + +void test_checkout_head__typechange_workdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target; + struct stat st; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/new.txt", 0755)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(p_stat("testrepo/new.txt", &st)); + cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); + + git_object_free(target); +} + +void test_checkout_head__typechange_index_and_workdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target; + git_index *index; + struct stat st; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/new.txt", 0755)); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "new.txt")); + cl_git_pass(git_index_write(index)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(p_stat("testrepo/new.txt", &st)); + cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); + + git_object_free(target); + git_index_free(index); +} + +void test_checkout_head__workdir_filemode_is_simplified(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "a38d028f71eaa590febb7d716b1ca32350cf70da")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/branch_file.txt", 0666)); + + /* + * Checkout should not fail with a conflict; though the file mode + * on disk is literally different to the base (0666 vs 0644), Git + * ignores the actual mode and simply treats both as non-executable. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} + +void test_checkout_head__obeys_filemode_true(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* In this commit, `README` is executable */ + cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_repo_set_bool(g_repo, "core.filemode", true); + cl_must_pass(p_chmod("testrepo/README", 0644)); + + /* + * Checkout will fail with a conflict; the file mode is updated in + * the checkout target, but the contents have changed in our branch. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} + +void test_checkout_head__obeys_filemode_false(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* In this commit, `README` is executable */ + cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_repo_set_bool(g_repo, "core.filemode", false); + cl_must_pass(p_chmod("testrepo/README", 0644)); + + /* + * Checkout will fail with a conflict; the file contents are updated + * in the checkout target, but the filemode has changed in our branch. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index b3b860c63..a7e29b3db 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -10,6 +10,16 @@ static git_repository *g_repo; static git_checkout_options g_opts; static git_object *g_object; +static void assert_status_entrycount(git_repository *repo, size_t count) +{ + git_status_list *status; + + cl_git_pass(git_status_list_new(&status, repo, NULL)); + cl_assert_equal_i(count, git_status_list_entrycount(status)); + + git_status_list_free(status); +} + void test_checkout_tree__initialize(void) { g_repo = cl_git_sandbox_init("testrepo"); @@ -1086,6 +1096,8 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); git_commit_free(commit); +#else + cl_skip(); #endif } @@ -1480,7 +1492,6 @@ void test_checkout_tree__baseline_is_empty_when_no_index(void) git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; git_reference *head; git_object *obj; - git_status_list *status; size_t conflicts = 0; assert_on_branch(g_repo, "master"); @@ -1506,14 +1517,49 @@ void test_checkout_tree__baseline_is_empty_when_no_index(void) opts.checkout_strategy |= GIT_CHECKOUT_FORCE; cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_status_list_new(&status, g_repo, NULL)); - cl_assert_equal_i(0, git_status_list_entrycount(status)); - git_status_list_free(status); + assert_status_entrycount(g_repo, 0); git_object_free(obj); git_reference_free(head); } +void test_checkout_tree__mode_change_is_force_updated(void) +{ + git_index *index; + git_reference *head; + git_object *obj; + + if (!cl_is_chmod_supported()) + clar__skip(); + + assert_on_branch(g_repo, "master"); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel(&obj, head, GIT_OBJ_COMMIT)); + + cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); + assert_status_entrycount(g_repo, 0); + + /* update the mode on-disk */ + cl_must_pass(p_chmod("testrepo/README", 0755)); + + assert_status_entrycount(g_repo, 1); + cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); + assert_status_entrycount(g_repo, 0); + + /* update the mode on-disk and in the index */ + cl_must_pass(p_chmod("testrepo/README", 0755)); + cl_must_pass(git_index_add_bypath(index, "README")); + + assert_status_entrycount(g_repo, 1); + cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); + assert_status_entrycount(g_repo, 0); + + git_object_free(obj); + git_reference_free(head); + git_index_free(index); +} + void test_checkout_tree__nullopts(void) { cl_git_pass(git_checkout_tree(g_repo, NULL, NULL)); diff --git a/tests/clar.c b/tests/clar.c index 905d67db7..d5212d1ca 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -313,11 +313,18 @@ clar_parse_args(int argc, char **argv) { int i; + /* Verify options before execute */ for (i = 1; i < argc; ++i) { char *argument = argv[i]; - if (argument[0] != '-') + if (argument[0] != '-' || argument[1] == '\0' + || strchr("sixvqQl", argument[1]) == NULL) { clar_usage(argv[0]); + } + } + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; switch (argument[1]) { case 's': @@ -391,7 +398,7 @@ clar_parse_args(int argc, char **argv) break; default: - clar_usage(argv[0]); + assert(!"Unexpected commandline argument!"); } } } diff --git a/tests/clar/print.h b/tests/clar/print.h index 6529b6b4c..916d807c1 100644 --- a/tests/clar/print.h +++ b/tests/clar/print.h @@ -3,7 +3,7 @@ static void clar_print_init(int test_count, int suite_count, const char *suite_n { (void)test_count; printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); - printf("Started\n"); + printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); } static void clar_print_shutdown(int test_count, int suite_count, int error_count) diff --git a/tests/commit/signature.c b/tests/commit/signature.c index 30bc92967..286079fa2 100644 --- a/tests/commit/signature.c +++ b/tests/commit/signature.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "signature.h" static int try_build_signature(const char *name, const char *email, git_time_t time, int offset) { @@ -99,3 +100,29 @@ void test_commit_signature__from_buf(void) git_signature_free(sign); } +void test_commit_signature__from_buf_with_neg_zero_offset(void) +{ + git_signature *sign; + + cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 -0000")); + cl_assert_equal_s("Test User", sign->name); + cl_assert_equal_s("test@test.tt", sign->email); + cl_assert_equal_i(1461698487, sign->when.time); + cl_assert_equal_i(0, sign->when.offset); + cl_assert_equal_i('-', sign->when.sign); + git_signature_free(sign); +} + +void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void) +{ + git_signature *with_neg_zero; + git_signature *with_pos_zero; + + cl_git_pass(git_signature_from_buffer(&with_neg_zero, "Test User 1461698487 -0000")); + cl_git_pass(git_signature_from_buffer(&with_pos_zero, "Test User 1461698487 +0000")); + + cl_assert(!git_signature__equal(with_neg_zero, with_pos_zero)); + + git_signature_free((git_signature *)with_neg_zero); + git_signature_free((git_signature *)with_pos_zero); +} diff --git a/tests/config/backend.c b/tests/config/backend.c index 3fd6eb114..18c4ca59e 100644 --- a/tests/config/backend.c +++ b/tests/config/backend.c @@ -10,13 +10,13 @@ void test_config_backend__checks_version(void) backend.version = 1024; cl_git_pass(git_config_new(&cfg)); - cl_git_fail(git_config_add_backend(cfg, &backend, 0, false)); + cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); err = giterr_last(); cl_assert_equal_i(GITERR_INVALID, err->klass); giterr_clear(); backend.version = 1024; - cl_git_fail(git_config_add_backend(cfg, &backend, 0, false)); + cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); err = giterr_last(); cl_assert_equal_i(GITERR_INVALID, err->klass); diff --git a/tests/config/conditionals.c b/tests/config/conditionals.c new file mode 100644 index 000000000..3a87de21f --- /dev/null +++ b/tests/config/conditionals.c @@ -0,0 +1,103 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "fileops.h" + +#ifdef GIT_WIN32 +# define ROOT_PREFIX "C:" +#else +# define ROOT_PREFIX +#endif + +static git_repository *_repo; + +void test_config_conditionals__initialize(void) +{ + _repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_config_conditionals__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_condition_includes(const char *keyword, const char *path, bool expected) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path); + git_buf_puts(&buf, "path = other\n"); + + cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); + cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); + _repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_repository_config(&cfg, _repo)); + + if (expected) { + git_buf_clear(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_assert_equal_s("baz", git_buf_cstr(&buf)); + } else { + cl_git_fail_with(GIT_ENOTFOUND, + git_config_get_string_buf(&buf, cfg, "foo.bar")); + } + + git_buf_free(&buf); + git_config_free(cfg); +} + +void test_config_conditionals__gitdir(void) +{ + git_buf path = GIT_BUF_INIT; + char *sandbox_path; + + assert_condition_includes("gitdir", ROOT_PREFIX "/", true); + assert_condition_includes("gitdir", "empty_standard_repo", true); + assert_condition_includes("gitdir", "empty_standard_repo/", true); + assert_condition_includes("gitdir", "./", true); + + assert_condition_includes("gitdir", ROOT_PREFIX "/nonexistent", false); + assert_condition_includes("gitdir", ROOT_PREFIX "/empty_standard_repo", false); + assert_condition_includes("gitdir", "empty_stand", false); + assert_condition_includes("gitdir", "~/empty_standard_repo", false); + + sandbox_path = p_realpath(clar_sandbox_path(), NULL); + + git_buf_joinpath(&path, sandbox_path, "/"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, sandbox_path, "/*"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, sandbox_path, "empty_standard_repo"); + assert_condition_includes("gitdir", path.ptr, true); + + git_buf_joinpath(&path, sandbox_path, "Empty_Standard_Repo"); + assert_condition_includes("gitdir", path.ptr, false); + + git__free(sandbox_path); + git_buf_free(&path); +} + +void test_config_conditionals__gitdir_i(void) +{ + git_buf path = GIT_BUF_INIT; + char *sandbox_path; + + sandbox_path = p_realpath(clar_sandbox_path(), NULL); + + git_buf_joinpath(&path, sandbox_path, "empty_standard_repo"); + assert_condition_includes("gitdir/i", path.ptr, true); + + git_buf_joinpath(&path, sandbox_path, "EMPTY_STANDARD_REPO"); + assert_condition_includes("gitdir/i", path.ptr, true); + + git__free(sandbox_path); + git_buf_free(&path); +} + +void test_config_conditionals__invalid_conditional_fails(void) +{ + assert_condition_includes("foobar", ".git", false); +} diff --git a/tests/config/configlevel.c b/tests/config/configlevel.c index ca478b1a5..b73656cb9 100644 --- a/tests/config/configlevel.c +++ b/tests/config/configlevel.c @@ -7,11 +7,11 @@ void test_config_configlevel__adding_the_same_level_twice_returns_EEXISTS(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); error = git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_GLOBAL, 0); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); cl_git_fail(error); cl_assert_equal_i(GIT_EEXISTS, error); @@ -26,9 +26,9 @@ void test_config_configlevel__can_replace_a_config_file_at_an_existing_level(voi cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_LOCAL, 1)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_LOCAL, 1)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); cl_assert_equal_s("don't find me!", buf.ptr); @@ -45,9 +45,9 @@ void test_config_configlevel__can_read_from_a_single_level_focused_file_after_pa cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); diff --git a/tests/config/include.c b/tests/config/include.c index 0a07c9b85..e440b9a78 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -2,25 +2,31 @@ #include "buffer.h" #include "fileops.h" +static git_config *cfg; +static git_buf buf; + +void test_config_include__initialize(void) +{ + cfg = NULL; + git_buf_init(&buf, 0); +} + +void test_config_include__cleanup(void) +{ + git_config_free(cfg); + git_buf_free(&buf); +} + void test_config_include__relative(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include"))); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); - - git_buf_free(&buf); - git_config_free(cfg); } void test_config_include__absolute(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config"))); cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf)); @@ -29,16 +35,10 @@ void test_config_include__absolute(void) cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); - - git_buf_free(&buf); - git_config_free(cfg); } void test_config_include__homedir(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included"); @@ -47,18 +47,12 @@ void test_config_include__homedir(void) cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); - git_buf_free(&buf); - git_config_free(cfg); - cl_sandbox_set_search_path_defaults(); } /* We need to pretend that the variables were defined where the file was included */ void test_config_include__ordering(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya"); cl_git_mkfile("including", "[foo \"bar\"]\nfrotz = hello\n" @@ -72,16 +66,11 @@ void test_config_include__ordering(void) git_buf_clear(&buf); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); cl_assert_equal_s("huzzah", git_buf_cstr(&buf)); - - git_buf_free(&buf); - git_config_free(cfg); } /* We need to pretend that the variables were defined where the file was included */ void test_config_include__depth(void) { - git_config *cfg; - cl_git_mkfile("a", "[include]\npath = b"); cl_git_mkfile("b", "[include]\npath = a"); @@ -93,9 +82,6 @@ void test_config_include__depth(void) void test_config_include__missing(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_mkfile("including", "[include]\npath = nonexistentfile\n[foo]\nbar = baz"); giterr_clear(); @@ -103,16 +89,10 @@ void test_config_include__missing(void) cl_assert(giterr_last() == NULL); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", git_buf_cstr(&buf)); - - git_buf_free(&buf); - git_config_free(cfg); } void test_config_include__missing_homedir(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); cl_git_mkfile("including", "[include]\npath = ~/.nonexistentfile\n[foo]\nbar = baz"); @@ -122,17 +102,12 @@ void test_config_include__missing_homedir(void) cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); cl_assert_equal_s("baz", git_buf_cstr(&buf)); - git_buf_free(&buf); - git_config_free(cfg); - cl_sandbox_set_search_path_defaults(); } #define replicate10(s) s s s s s s s s s s void test_config_include__depth2(void) { - git_config *cfg; - git_buf buf = GIT_BUF_INIT; const char *content = "[include]\n" replicate10(replicate10("path=bottom\n")); cl_git_mkfile("top-level", "[include]\npath = middle\n[foo]\nbar = baz"); @@ -147,7 +122,45 @@ void test_config_include__depth2(void) git_buf_clear(&buf); cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar2")); cl_assert_equal_s("baz2", git_buf_cstr(&buf)); - - git_buf_free(&buf); - git_config_free(cfg); +} + +void test_config_include__removing_include_removes_values(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included"); + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_mkfile("top-level", ""); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); +} + +void test_config_include__rewriting_include_refreshes_values(void) +{ + cl_git_mkfile("top-level", "[include]\npath = first\n[include]\npath = second"); + cl_git_mkfile("first", "[first]\nfoo = bar"); + cl_git_mkfile("second", "[second]\nfoo = bar"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_mkfile("first", "[first]\nother = value"); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "first.other")); + cl_assert_equal_s(buf.ptr, "value"); +} + +void test_config_include__included_variables_cannot_be_deleted(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included\n"); + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_fail(git_config_delete_entry(cfg, "foo.bar")); +} + +void test_config_include__included_variables_cannot_be_modified(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included\n"); + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_fail(git_config_set_string(cfg, "foo.bar", "other-value")); } diff --git a/tests/config/multivar.c b/tests/config/multivar.c index d1b8c4cda..4f08a4817 100644 --- a/tests/config/multivar.c +++ b/tests/config/multivar.c @@ -94,27 +94,27 @@ void test_config_multivar__get(void) check_get_multivar_foreach(cfg, 2, 1); /* add another that has the _name entry */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); check_get_multivar_foreach(cfg, 3, 2); /* add another that does not have the _name entry */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, NULL, 1)); check_get_multivar_foreach(cfg, 3, 2); /* add another that does not have the _name entry at the end */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, NULL, 1)); check_get_multivar_foreach(cfg, 3, 2); /* drop original file */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); check_get_multivar_foreach(cfg, 1, 1); /* drop other file with match */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); check_get_multivar_foreach(cfg, 0, 0); /* reload original file (add different place in order) */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); check_get_multivar_foreach(cfg, 2, 1); check_get_multivar(cfg, 2); diff --git a/tests/config/read.c b/tests/config/read.c index f86b2d79e..a34455a0c 100644 --- a/tests/config/read.c +++ b/tests/config/read.c @@ -289,9 +289,9 @@ void test_config_read__foreach(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); count = 0; cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); @@ -313,9 +313,9 @@ void test_config_read__iterator(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); count = 0; cl_git_pass(git_config_iterator_new(&iter, cfg)); @@ -445,7 +445,7 @@ void test_config_read__read_git_config_entry(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_get_entry(&entry, cfg, "core.dummy2")); cl_assert_equal_s("core.dummy2", entry->name); @@ -469,11 +469,11 @@ void test_config_read__local_config_overrides_global_config_overrides_system_con cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); cl_assert_equal_i(28, i); @@ -482,9 +482,9 @@ void test_config_read__local_config_overrides_global_config_overrides_system_con cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); cl_assert_equal_i(7, i); @@ -510,11 +510,11 @@ void test_config_read__fallback_from_local_to_global_and_from_global_to_system(v cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_get_int32(&i, cfg, "core.global")); cl_assert_equal_i(17, i); @@ -546,9 +546,9 @@ void test_config_read__simple_read_from_specific_level(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_SYSTEM, 0)); + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); @@ -703,3 +703,48 @@ void test_config_read__path(void) git_buf_free(&expected_path); git_config_free(cfg); } + +void test_config_read__crlf_style_line_endings(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_free(&buf); +} + +void test_config_read__trailing_crlf(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n\r\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_free(&buf); +} + +void test_config_read__bom(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some]\n var = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_free(&buf); +} diff --git a/tests/config/readonly.c b/tests/config/readonly.c index f45abdd29..a424922c1 100644 --- a/tests/config/readonly.c +++ b/tests/config/readonly.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "config_file.h" #include "config.h" +#include "path.h" static git_config *cfg; @@ -21,7 +22,7 @@ void test_config_readonly__writing_to_readonly_fails(void) cl_git_pass(git_config_file__ondisk(&backend, "global")); backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); cl_assert(!git_path_exists("global")); @@ -33,10 +34,10 @@ void test_config_readonly__writing_to_cfg_with_rw_precedence_succeeds(void) cl_git_pass(git_config_file__ondisk(&backend, "global")); backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_file__ondisk(&backend, "local")); - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, 0)); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); @@ -51,10 +52,10 @@ void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void) cl_git_pass(git_config_file__ondisk(&backend, "local")); backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, 0)); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_file__ondisk(&backend, "global")); - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); diff --git a/tests/config/write.c b/tests/config/write.c index 56ef2e9fb..6687ba1f7 100644 --- a/tests/config/write.c +++ b/tests/config/write.c @@ -93,9 +93,9 @@ void test_config_write__delete_value_at_specific_level(void) cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, "config9", - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, "config15", - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); @@ -368,9 +368,9 @@ void test_config_write__add_value_at_specific_level(void) // open config15 as global level config file cl_git_pass(git_config_new(&cfg)); cl_git_pass(git_config_add_file_ondisk(cfg, "config9", - GIT_CONFIG_LEVEL_LOCAL, 0)); + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); cl_git_pass(git_config_add_file_ondisk(cfg, "config15", - GIT_CONFIG_LEVEL_GLOBAL, 0)); + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); @@ -722,3 +722,26 @@ void test_config_write__repeated(void) git_config_free(cfg); } + +void test_config_write__preserve_case(void) +{ + const char *filename = "config-preserve-case"; + git_config *cfg; + git_buf result = GIT_BUF_INIT; + const char *expected = "[sOMe]\n" \ + "\tThInG = foo\n" \ + "\tOtheR = thing\n"; + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_set_string(cfg, "sOMe.ThInG", "foo")); + cl_git_pass(git_config_set_string(cfg, "SomE.OtheR", "thing")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + + cl_git_pass(git_futils_readbuffer(&result, filename)); + cl_assert_equal_s(expected, result.ptr); + git_buf_free(&result); + + git_config_free(cfg); +} diff --git a/tests/core/stream.c b/tests/core/stream.c index 0cbf44230..9bed4ae27 100644 --- a/tests/core/stream.c +++ b/tests/core/stream.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include "git2/sys/stream.h" -#include "tls_stream.h" +#include "streams/tls.h" #include "stream.h" static git_stream test_stream; @@ -37,8 +37,7 @@ void test_core_stream__register_tls(void) * or when openssl support is disabled (except on OSX * with Security framework). */ -#if defined(GIT_WIN32) || \ - (!defined(GIT_SECURE_TRANSPORT) && !defined(GIT_OPENSSL)) +#if defined(GIT_WIN32) || !defined(GIT_HTTPS) cl_git_fail_with(-1, error); #else cl_git_pass(error); diff --git a/tests/core/string.c b/tests/core/string.c index 90e8fa027..85db0c662 100644 --- a/tests/core/string.c +++ b/tests/core/string.c @@ -40,6 +40,48 @@ void test_core_string__2(void) cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); } +/* compare prefixes with len */ +void test_core_string__prefixncmp(void) +{ + cl_assert(git__prefixncmp("", 0, "") == 0); + cl_assert(git__prefixncmp("a", 1, "") == 0); + cl_assert(git__prefixncmp("", 0, "a") < 0); + cl_assert(git__prefixncmp("a", 1, "b") < 0); + cl_assert(git__prefixncmp("b", 1, "a") > 0); + cl_assert(git__prefixncmp("ab", 2, "a") == 0); + cl_assert(git__prefixncmp("ab", 1, "a") == 0); + cl_assert(git__prefixncmp("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp("a", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp("ab", 1, "aa") < 0); +} + +/* compare prefixes with len */ +void test_core_string__prefixncmp_icase(void) +{ + cl_assert(git__prefixncmp_icase("", 0, "") == 0); + cl_assert(git__prefixncmp_icase("a", 1, "") == 0); + cl_assert(git__prefixncmp_icase("", 0, "a") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("A", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "B") < 0); + cl_assert(git__prefixncmp_icase("b", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("B", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("b", 1, "A") > 0); + cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0); + cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0); +} + void test_core_string__strcmp(void) { cl_assert(git__strcmp("", "") == 0); diff --git a/tests/core/structinit.c b/tests/core/structinit.c index 78503fcc6..8feba864e 100644 --- a/tests/core/structinit.c +++ b/tests/core/structinit.c @@ -176,4 +176,8 @@ void test_core_structinit__compare(void) CHECK_MACRO_FUNC_INIT_EQUAL( \ git_proxy_options, GIT_PROXY_OPTIONS_VERSION, \ GIT_PROXY_OPTIONS_INIT, git_proxy_init_options); + + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_VERSION, \ + GIT_DIFF_PATCHID_OPTIONS_INIT, git_diff_patchid_init_options); } diff --git a/tests/diff/blob.c b/tests/diff/blob.c index c3933c313..05cc28218 100644 --- a/tests/diff/blob.c +++ b/tests/diff/blob.c @@ -1,6 +1,19 @@ #include "clar_libgit2.h" #include "diff_helpers.h" +#define BLOB_DIFF \ + "diff --git a/file b/file\n" \ + "index 45141a7..4d713dc 100644\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -1 +1,6 @@\n" \ + " Hello from the root\n" \ + "+\n" \ + "+Some additional lines\n" \ + "+\n" \ + "+Down here below\n" \ + "+\n" + static git_repository *g_repo = NULL; static diff_expects expected; static git_diff_options opts; @@ -65,6 +78,32 @@ static void assert_one_modified( cl_assert_equal_i(dels, exp->line_dels); } +void test_diff_blob__patch_with_freed_blobs(void) +{ + git_oid a_oid, b_oid; + git_blob *a, *b; + git_patch *p; + git_buf buf = GIT_BUF_INIT; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); + + cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, NULL)); + + git_blob_free(a); + git_blob_free(b); + + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s(buf.ptr, BLOB_DIFF); + + git_patch_free(p); + git_buf_free(&buf); +} + void test_diff_blob__can_compare_text_blobs(void) { git_blob *a, *b, *c; diff --git a/tests/diff/parse.c b/tests/diff/parse.c index acb6eb8a5..dc2ceefec 100644 --- a/tests/diff/parse.c +++ b/tests/diff/parse.c @@ -57,6 +57,27 @@ static void test_parse_invalid_diff(const char *invalid_diff) git_buf_free(&buf); } +void test_diff_parse__exact_rename(void) +{ + const char *content = + "---\n" + " old_name.c => new_name.c | 0\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" + " rename old_name.c => new_name.c (100%)\n" + "\n" + "diff --git a/old_name.c b/new_name.c\n" + "similarity index 100%\n" + "rename from old_name.c\n" + "rename to new_name.c\n" + "-- \n" + "2.9.3\n"; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + void test_diff_parse__invalid_patches_fails(void) { test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE); diff --git a/tests/diff/patchid.c b/tests/diff/patchid.c new file mode 100644 index 000000000..75a2aa814 --- /dev/null +++ b/tests/diff/patchid.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "patch/patch_common.h" + +static void verify_patch_id(const char *diff_content, const char *expected_id) +{ + git_oid expected_oid, actual_oid; + git_diff *diff; + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_id)); + cl_git_pass(git_diff_from_buffer(&diff, diff_content, strlen(diff_content))); + cl_git_pass(git_diff_patchid(&actual_oid, diff, NULL)); + + cl_assert_equal_oid(&expected_oid, &actual_oid); + + git_diff_free(diff); +} + +void test_diff_patchid__simple_commit(void) +{ + verify_patch_id(PATCH_SIMPLE_COMMIT, "06094b1948b878b7d9ff7560b4eae672a014b0ec"); +} + +void test_diff_patchid__filename_with_spaces(void) +{ + verify_patch_id(PATCH_APPEND_NO_NL, "f0ba05413beaef743b630e796153839462ee477a"); +} + +void test_diff_patchid__multiple_hunks(void) +{ + verify_patch_id(PATCH_MULTIPLE_HUNKS, "81e26c34643d17f521e57c483a6a637e18ba1f57"); +} + +void test_diff_patchid__multiple_files(void) +{ + verify_patch_id(PATCH_MULTIPLE_FILES, "192d1f49d23f2004517963aecd3f8a6c467f50ff"); +} + +void test_diff_patchid__same_diff_with_differing_whitespace_has_same_id(void) +{ + const char *tabs = + "diff --git a/file.txt b/file.txt\n" + "index 8fecc09..1d43a92 100644\n" + "--- a/file.txt\n" + "+++ b/file.txt\n" + "@@ -1 +1 @@\n" + "-old text\n" + "+ new text\n"; + const char *spaces = + "diff --git a/file.txt b/file.txt\n" + "index 8fecc09..1d43a92 100644\n" + "--- a/file.txt\n" + "+++ b/file.txt\n" + "@@ -1 +1 @@\n" + "-old text\n" + "+ new text\n"; + const char *id = "11efdd13c30f7a1056eac2ae2fb952da475e2c23"; + + verify_patch_id(tabs, id); + verify_patch_id(spaces, id); +} diff --git a/tests/diff/rename.c b/tests/diff/rename.c index c1cd25239..ddc1d5d78 100644 --- a/tests/diff/rename.c +++ b/tests/diff/rename.c @@ -16,6 +16,13 @@ void test_diff_rename__cleanup(void) cl_git_sandbox_cleanup(); } +#define INITIAL_COMMIT "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2" +#define COPY_RENAME_COMMIT "2bc7f351d20b53f1c72c16c4b036e491c478c49a" +#define REWRITE_COPY_COMMIT "1c068dee5790ef1580cfc4cd670915b48d790084" +#define RENAME_MODIFICATION_COMMIT "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13" +#define REWRITE_DELETE_COMMIT "84d8efa38af7ace2b302de0adbda16b1f1cd2e1b" +#define DELETE_RENAME_COMMIT "be053a189b0bbde545e0a3f59ce00b46ad29ce0d" + /* * Renames repo has: * @@ -36,12 +43,22 @@ void test_diff_rename__cleanup(void) * ikeepsix.txt -> ikeepsix.txt (reorder sections in file) * sixserving.txt -> sixserving.txt (whitespace change - not just indent) * sevencities.txt -> songof7cities.txt (rename, small text changes) + * commit 84d8efa38af7ace2b302de0adbda16b1f1cd2e1b + * songof7cities.txt -> songof7citie.txt (major rewrite, <20% match) + * ikeepsix.txt -> (deleted) + * untimely.txt (no change) + * sixserving.txt (no change) + * commit be053a189b0bbde545e0a3f59ce00b46ad29ce0d + * ikeepsix.txt -> (deleted) + * songof7cities.txt -> ikeepsix.txt (rename, 100% match) + * untimely.txt (no change) + * sixserving.txt (no change) */ void test_diff_rename__match_oid(void) { - const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; - const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; git_tree *old_tree, *new_tree; git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; @@ -139,8 +156,8 @@ void test_diff_rename__match_oid(void) void test_diff_rename__checks_options_version(void) { - const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; - const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; git_tree *old_tree, *new_tree; git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; @@ -171,9 +188,9 @@ void test_diff_rename__checks_options_version(void) void test_diff_rename__not_exact_match(void) { - const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; - const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; - const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; + const char *sha0 = COPY_RENAME_COMMIT; + const char *sha1 = REWRITE_COPY_COMMIT; + const char *sha2 = RENAME_MODIFICATION_COMMIT; git_tree *old_tree, *new_tree; git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; @@ -433,7 +450,7 @@ void test_diff_rename__test_small_files(void) void test_diff_rename__working_directory_changes(void) { - const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *sha0 = COPY_RENAME_COMMIT; const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba"; git_oid id; git_tree *tree; @@ -592,8 +609,8 @@ void test_diff_rename__working_directory_changes(void) void test_diff_rename__patch(void) { - const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; - const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + const char *sha0 = COPY_RENAME_COMMIT; + const char *sha1 = REWRITE_COPY_COMMIT; git_tree *old_tree, *new_tree; git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; @@ -1425,9 +1442,9 @@ void test_diff_rename__can_delete_unmodified_deltas(void) void test_diff_rename__matches_config_behavior(void) { - const char *sha0 = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; - const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; - const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + const char *sha0 = INITIAL_COMMIT; + const char *sha1 = COPY_RENAME_COMMIT; + const char *sha2 = REWRITE_COPY_COMMIT; git_tree *tree0, *tree1, *tree2; git_config *cfg; @@ -1508,8 +1525,8 @@ void test_diff_rename__matches_config_behavior(void) void test_diff_rename__can_override_thresholds_when_obeying_config(void) { - const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; - const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + const char *sha1 = COPY_RENAME_COMMIT; + const char *sha2 = REWRITE_COPY_COMMIT; git_tree *tree1, *tree2; git_config *cfg; @@ -1563,8 +1580,8 @@ void test_diff_rename__can_override_thresholds_when_obeying_config(void) void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void) { - const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; - const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; + const char *sha1 = REWRITE_COPY_COMMIT; + const char *sha2 = RENAME_MODIFICATION_COMMIT; git_tree *tree1, *tree2; git_config *cfg; @@ -1710,8 +1727,8 @@ void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void */ void test_diff_rename__identical(void) { - const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; - const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; git_tree *old_tree, *new_tree; git_diff *diff; git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; @@ -1748,3 +1765,216 @@ void test_diff_rename__identical(void) git_tree_free(new_tree); } +void test_diff_rename__rewrite_and_delete(void) +{ + const char *old_sha = RENAME_MODIFICATION_COMMIT; + const char *new_sha = REWRITE_DELETE_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/ikeepsix.txt b/ikeepsix.txt\n" + "deleted file mode 100644\n" + "index eaf4a3e..0000000\n" + "--- a/ikeepsix.txt\n" + "+++ /dev/null\n" + "@@ -1,27 +0,0 @@\n" + "-I Keep Six Honest Serving-Men\n" + "-=============================\n" + "-\n" + "-She sends'em abroad on her own affairs,\n" + "- From the second she opens her eyes—\n" + "-One million Hows, two million Wheres,\n" + "-And seven million Whys!\n" + "-\n" + "-I let them rest from nine till five,\n" + "- For I am busy then,\n" + "-As well as breakfast, lunch, and tea,\n" + "- For they are hungry men.\n" + "-But different folk have different views;\n" + "-I know a person small—\n" + "-She keeps ten million serving-men,\n" + "-Who get no rest at all!\n" + "-\n" + "- -- Rudyard Kipling\n" + "-\n" + "-I KEEP six honest serving-men\n" + "- (They taught me all I knew);\n" + "-Their names are What and Why and When\n" + "- And How and Where and Who.\n" + "-I send them over land and sea,\n" + "- I send them east and west;\n" + "-But after they have worked for me,\n" + "- I give them all a rest.\n" + "diff --git a/songof7cities.txt b/songof7cities.txt\n" + "index 4210ffd..95ceb12 100644\n" + "--- a/songof7cities.txt\n" + "+++ b/songof7cities.txt\n" + "@@ -1,45 +1,45 @@\n" + "-The Song of Seven Cities\n" + "+THE SONG OF SEVEN CITIES\n" + " ------------------------\n" + " \n" + "-I WAS Lord of Cities very sumptuously builded.\n" + "-Seven roaring Cities paid me tribute from afar.\n" + "-Ivory their outposts were--the guardrooms of them gilded,\n" + "-And garrisoned with Amazons invincible in war.\n" + "-\n" + "-All the world went softly when it walked before my Cities--\n" + "-Neither King nor Army vexed my peoples at their toil,\n" + "-Never horse nor chariot irked or overbore my Cities,\n" + "-Never Mob nor Ruler questioned whence they drew their spoil.\n" + "-\n" + "-Banded, mailed and arrogant from sunrise unto sunset;\n" + "-Singing while they sacked it, they possessed the land at large.\n" + "-Yet when men would rob them, they resisted, they made onset\n" + "-And pierced the smoke of battle with a thousand-sabred charge.\n" + "-\n" + "-So they warred and trafficked only yesterday, my Cities.\n" + "-To-day there is no mark or mound of where my Cities stood.\n" + "-For the River rose at midnight and it washed away my Cities.\n" + "-They are evened with Atlantis and the towns before the Flood.\n" + "-\n" + "-Rain on rain-gorged channels raised the water-levels round them,\n" + "-Freshet backed on freshet swelled and swept their world from sight,\n" + "-Till the emboldened floods linked arms and, flashing forward, drowned them--\n" + "-Drowned my Seven Cities and their peoples in one night!\n" + "-\n" + "-Low among the alders lie their derelict foundations,\n" + "-The beams wherein they trusted and the plinths whereon they built--\n" + "-My rulers and their treasure and their unborn populations,\n" + "-Dead, destroyed, aborted, and defiled with mud and silt!\n" + "-\n" + "-The Daughters of the Palace whom they cherished in my Cities,\n" + "-My silver-tongued Princesses, and the promise of their May--\n" + "-Their bridegrooms of the June-tide--all have perished in my Cities,\n" + "-With the harsh envenomed virgins that can neither love nor play.\n" + "-\n" + "-I was Lord of Cities--I will build anew my Cities,\n" + "-Seven, set on rocks, above the wrath of any flood.\n" + "-Nor will I rest from search till I have filled anew my Cities\n" + "-With peoples undefeated of the dark, enduring blood.\n" + "+I WAS LORD OF CITIES VERY SUMPTUOUSLY BUILDED.\n" + "+SEVEN ROARING CITIES PAID ME TRIBUTE FROM AFAR.\n" + "+IVORY THEIR OUTPOSTS WERE--THE GUARDROOMS OF THEM GILDED,\n" + "+AND GARRISONED WITH AMAZONS INVINCIBLE IN WAR.\n" + "+\n" + "+ALL THE WORLD WENT SOFTLY WHEN IT WALKED BEFORE MY CITIES--\n" + "+NEITHER KING NOR ARMY VEXED MY PEOPLES AT THEIR TOIL,\n" + "+NEVER HORSE NOR CHARIOT IRKED OR OVERBORE MY CITIES,\n" + "+NEVER MOB NOR RULER QUESTIONED WHENCE THEY DREW THEIR SPOIL.\n" + "+\n" + "+BANDED, MAILED AND ARROGANT FROM SUNRISE UNTO SUNSET;\n" + "+SINGING WHILE THEY SACKED IT, THEY POSSESSED THE LAND AT LARGE.\n" + "+YET WHEN MEN WOULD ROB THEM, THEY RESISTED, THEY MADE ONSET\n" + "+AND PIERCED THE SMOKE OF BATTLE WITH A THOUSAND-SABRED CHARGE.\n" + "+\n" + "+SO THEY WARRED AND TRAFFICKED ONLY YESTERDAY, MY CITIES.\n" + "+TO-DAY THERE IS NO MARK OR MOUND OF WHERE MY CITIES STOOD.\n" + "+FOR THE RIVER ROSE AT MIDNIGHT AND IT WASHED AWAY MY CITIES.\n" + "+THEY ARE EVENED WITH ATLANTIS AND THE TOWNS BEFORE THE FLOOD.\n" + "+\n" + "+RAIN ON RAIN-GORGED CHANNELS RAISED THE WATER-LEVELS ROUND THEM,\n" + "+FRESHET BACKED ON FRESHET SWELLED AND SWEPT THEIR WORLD FROM SIGHT,\n" + "+TILL THE EMBOLDENED FLOODS LINKED ARMS AND, FLASHING FORWARD, DROWNED THEM--\n" + "+DROWNED MY SEVEN CITIES AND THEIR PEOPLES IN ONE NIGHT!\n" + "+\n" + "+LOW AMONG THE ALDERS LIE THEIR DERELICT FOUNDATIONS,\n" + "+THE BEAMS WHEREIN THEY TRUSTED AND THE PLINTHS WHEREON THEY BUILT--\n" + "+MY RULERS AND THEIR TREASURE AND THEIR UNBORN POPULATIONS,\n" + "+DEAD, DESTROYED, ABORTED, AND DEFILED WITH MUD AND SILT!\n" + "+\n" + "+THE DAUGHTERS OF THE PALACE WHOM THEY CHERISHED IN MY CITIES,\n" + "+MY SILVER-TONGUED PRINCESSES, AND THE PROMISE OF THEIR MAY--\n" + "+THEIR BRIDEGROOMS OF THE JUNE-TIDE--ALL HAVE PERISHED IN MY CITIES,\n" + "+WITH THE HARSH ENVENOMED VIRGINS THAT CAN NEITHER LOVE NOR PLAY.\n" + "+\n" + "+I WAS LORD OF CITIES--I WILL BUILD ANEW MY CITIES,\n" + "+SEVEN, SET ON ROCKS, ABOVE THE WRATH OF ANY FLOOD.\n" + "+NOR WILL I REST FROM SEARCH TILL I HAVE FILLED ANEW MY CITIES\n" + "+WITH PEOPLES UNDEFEATED OF THE DARK, ENDURING BLOOD.\n" + " \n" + " To the sound of trumpets shall their seed restore my Cities\n" + " Wealthy and well-weaponed, that once more may I behold\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_free(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__delete_and_rename(void) +{ + const char *old_sha = RENAME_MODIFICATION_COMMIT; + const char *new_sha = DELETE_RENAME_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/sixserving.txt b/sixserving.txt\n" + "deleted file mode 100644\n" + "index f90d4fc..0000000\n" + "--- a/sixserving.txt\n" + "+++ /dev/null\n" + "@@ -1,25 +0,0 @@\n" + "-I KEEP six honest serving-men\n" + "- (They taught me all I knew);\n" + "-Their names are What and Why and When\n" + "- And How and Where and Who.\n" + "-I send them over land and sea,\n" + "- I send them east and west;\n" + "-But after they have worked for me,\n" + "- I give them all a rest.\n" + "-\n" + "-I let them rest from nine till five,\n" + "- For I am busy then,\n" + "-As well as breakfast, lunch, and tea,\n" + "- For they are hungry men.\n" + "-But different folk have different views;\n" + "-I know a person small—\n" + "-She keeps ten million serving-men,\n" + "-Who get no rest at all!\n" + "-\n" + "-She sends'em abroad on her own affairs,\n" + "- From the second she opens her eyes—\n" + "-One million Hows, two million Wheres,\n" + "-And seven million Whys!\n" + "-\n" + "- -- Rudyard Kipling\n" + "-\n" + "diff --git a/songof7cities.txt b/sixserving.txt\n" + "similarity index 100%\n" + "rename from songof7cities.txt\n" + "rename to sixserving.txt\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_free(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c index ea4b70e4a..4dabb577e 100644 --- a/tests/fetchhead/nonetwork.c +++ b/tests/fetchhead/nonetwork.c @@ -353,20 +353,25 @@ void test_fetchhead_nonetwork__quote_in_branch_name(void) } static bool found_master; -static bool find_master_called; +static bool found_haacked; +static bool find_master_haacked_called; -int find_master(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) +int find_master_haacked(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) { GIT_UNUSED(remote_url); GIT_UNUSED(oid); GIT_UNUSED(payload); - find_master_called = true; + find_master_haacked_called = true; if (!strcmp("refs/heads/master", ref_name)) { cl_assert(is_merge); found_master = true; } + if (!strcmp("refs/heads/haacked", ref_name)) { + cl_assert(is_merge); + found_haacked = true; + } return 0; } @@ -375,10 +380,12 @@ void test_fetchhead_nonetwork__create_when_refpecs_given(void) { git_remote *remote; git_buf path = GIT_BUF_INIT; - char *refspec = "refs/heads/master"; + char *refspec1 = "refs/heads/master"; + char *refspec2 = "refs/heads/haacked"; + char *refspecs[] = { refspec1, refspec2 }; git_strarray specs = { - &refspec, - 1, + refspecs, + 2, }; cl_set_cleanup(&cleanup_repository, "./test1"); @@ -391,9 +398,74 @@ void test_fetchhead_nonetwork__create_when_refpecs_given(void) cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL)); cl_assert(git_path_exists(path.ptr)); - cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master, NULL)); - cl_assert(find_master_called); + cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master_haacked, NULL)); + cl_assert(find_master_haacked_called); cl_assert(found_master); + cl_assert(found_haacked); + + git_remote_free(remote); + git_buf_free(&path); +} + +static bool count_refs_called; +struct prefix_count { + const char *prefix; + int count; + int expected; +}; + +int count_refs(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) +{ + int i; + struct prefix_count *prefix_counts = (struct prefix_count *) payload; + + GIT_UNUSED(remote_url); + GIT_UNUSED(oid); + GIT_UNUSED(is_merge); + + count_refs_called = true; + + for (i = 0; prefix_counts[i].prefix; i++) { + if (!git__prefixcmp(ref_name, prefix_counts[i].prefix)) + prefix_counts[i].count++; + } + + return 0; +} + +void test_fetchhead_nonetwork__create_with_multiple_refspecs(void) +{ + git_remote *remote; + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git"))); + git_remote_free(remote); + cl_git_pass(git_remote_add_fetch(g_repo, "origin", "+refs/notes/*:refs/origin/notes/*")); + /* Pick up the new refspec */ + cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); + + cl_git_pass(git_buf_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD")); + cl_assert(!git_path_exists(path.ptr)); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + cl_assert(git_path_exists(path.ptr)); + + { + int i; + struct prefix_count prefix_counts[] = { + {"refs/notes/", 0, 1}, + {"refs/heads/", 0, 12}, + {"refs/tags/", 0, 7}, + {NULL, 0, 0}, + }; + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, count_refs, &prefix_counts)); + cl_assert(count_refs_called); + for (i = 0; prefix_counts[i].prefix; i++) + cl_assert_equal_i(prefix_counts[i].expected, prefix_counts[i].count); + } git_remote_free(remote); git_buf_free(&path); diff --git a/tests/generate.py b/tests/generate.py index 587efb519..0a94d4952 100644 --- a/tests/generate.py +++ b/tests/generate.py @@ -8,7 +8,7 @@ from __future__ import with_statement from string import Template -import re, fnmatch, os, codecs, pickle +import re, fnmatch, os, sys, codecs, pickle class Module(object): class Template(object): @@ -128,8 +128,9 @@ class Module(object): class TestSuite(object): - def __init__(self, path): + def __init__(self, path, output): self.path = path + self.output = output def should_generate(self, path): if not os.path.isfile(path): @@ -157,7 +158,7 @@ class TestSuite(object): return modules def load_cache(self): - path = os.path.join(self.path, '.clarcache') + path = os.path.join(self.output, '.clarcache') cache = {} try: @@ -170,7 +171,7 @@ class TestSuite(object): return cache def save_cache(self): - path = os.path.join(self.path, '.clarcache') + path = os.path.join(self.output, '.clarcache') with open(path, 'wb') as cache: pickle.dump(self.modules, cache) @@ -200,22 +201,24 @@ class TestSuite(object): return sum(len(module.callbacks) for module in self.modules.values()) def write(self): - output = os.path.join(self.path, 'clar.suite') + output = os.path.join(self.output, 'clar.suite') if not self.should_generate(output): return False with open(output, 'w') as data: - for module in self.modules.values(): + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: t = Module.DeclarationTemplate(module) data.write(t.render()) - for module in self.modules.values(): + for module in modules: t = Module.CallbacksTemplate(module) data.write(t.render()) suites = "static struct clar_suite _clar_suites[] = {" + ','.join( - Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name) + Module.InfoTemplate(module).render() for module in modules ) + "\n};\n" data.write(suites) @@ -232,13 +235,18 @@ if __name__ == '__main__': parser = OptionParser() parser.add_option('-f', '--force', action="store_true", dest='force', default=False) parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) + parser.add_option('-o', '--output', dest='output') options, args = parser.parse_args() + if len(args) > 1: + print("More than one path given") + sys.exit(1) - for path in args or ['.']: - suite = TestSuite(path) - suite.load(options.force) - suite.disable(options.excluded) - if suite.write(): - print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) + path = args.pop() if args else '.' + output = options.output or path + suite = TestSuite(path, output) + suite.load(options.force) + suite.disable(options.excluded) + if suite.write(): + print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index 2efad5b33..2693b1312 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -256,3 +256,65 @@ void test_index_filemodes__invalid(void) git_index_free(index); } + +void test_index_filemodes__frombuffer_requires_files(void) +{ + git_index *index; + git_index_entry new_entry; + const git_index_entry *ret_entry; + const char *content = "hey there\n"; + + memset(&new_entry, 0, sizeof(new_entry)); + cl_git_pass(git_repository_index(&index, g_repo)); + + /* regular blob */ + new_entry.path = "dummy-file.txt"; + new_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_add_frombuffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); + cl_assert_equal_s("dummy-file.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_BLOB, ret_entry->mode); + + /* executable blob */ + new_entry.path = "dummy-file.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_git_pass(git_index_add_frombuffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); + cl_assert_equal_s("dummy-file.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, ret_entry->mode); + + /* links are also acceptable */ + new_entry.path = "dummy-link.txt"; + new_entry.mode = GIT_FILEMODE_LINK; + + cl_git_pass(git_index_add_frombuffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-link.txt", 0))); + cl_assert_equal_s("dummy-link.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_LINK, ret_entry->mode); + + /* trees are rejected */ + new_entry.path = "invalid_mode.txt"; + new_entry.mode = GIT_FILEMODE_TREE; + + cl_git_fail(git_index_add_frombuffer(index, + &new_entry, content, strlen(content))); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); + + /* submodules are rejected */ + new_entry.path = "invalid_mode.txt"; + new_entry.mode = GIT_FILEMODE_COMMIT; + + cl_git_fail(git_index_add_frombuffer(index, + &new_entry, content, strlen(content))); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); + + git_index_free(index); +} diff --git a/tests/index/tests.c b/tests/index/tests.c index 1498196b2..ea8335b48 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -856,11 +856,14 @@ void test_index_tests__change_icase_on_instance(void) void test_index_tests__can_lock_index(void) { + git_repository *repo; git_index *index; git_indexwriter one = GIT_INDEXWRITER_INIT, two = GIT_INDEXWRITER_INIT; - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_indexwriter_init(&one, index)); cl_git_fail_with(GIT_ELOCKED, git_indexwriter_init(&two, index)); @@ -873,4 +876,5 @@ void test_index_tests__can_lock_index(void) git_indexwriter_cleanup(&one); git_indexwriter_cleanup(&two); git_index_free(index); + cl_git_sandbox_cleanup(); } diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c index ae48fcd46..68d574126 100644 --- a/tests/iterator/iterator_helpers.c +++ b/tests/iterator/iterator_helpers.c @@ -51,8 +51,7 @@ void expect_iterator_items( cl_assert(entry->mode != GIT_FILEMODE_TREE); } - if (++count >= expected_flat) - break; + cl_assert(++count <= expected_flat); } assert_at_end(i, v); diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c index f33fd98f1..81016752c 100644 --- a/tests/iterator/workdir.c +++ b/tests/iterator/workdir.c @@ -610,6 +610,7 @@ void test_iterator_workdir__filesystem2(void) static const char *expect_base[] = { "heads/br2", "heads/dir", + "heads/executable", "heads/ident", "heads/long-file-name", "heads/master", @@ -630,7 +631,7 @@ void test_iterator_workdir__filesystem2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 15, expect_base, 15, expect_base); + expect_iterator_items(i, 16, expect_base, 16, expect_base); git_iterator_free(i); } @@ -662,7 +663,7 @@ void test_iterator_workdir__filesystem_gunk(void) /* should only have 13 items, since we're not asking for trees to be * returned. the goal of this test is simply to not crash. */ - expect_iterator_items(i, 13, NULL, 13, NULL); + expect_iterator_items(i, 15, NULL, 15, NULL); git_iterator_free(i); git_buf_free(&parent); } @@ -741,6 +742,8 @@ void test_iterator_workdir__skips_fifos_and_special_files(void) cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); git_iterator_free(i); +#else + cl_skip(); #endif } diff --git a/tests/main.c b/tests/main.c index f67c8ffbc..b4fccecc4 100644 --- a/tests/main.c +++ b/tests/main.c @@ -11,7 +11,12 @@ int main(int argc, char *argv[]) clar_test_init(argc, argv); - git_libgit2_init(); + res = git_libgit2_init(); + if (res < 0) { + fprintf(stderr, "failed to init libgit2"); + return res; + } + cl_global_trace_register(); cl_sandbox_set_search_path_defaults(); diff --git a/tests/merge/conflict_data.h b/tests/merge/conflict_data.h index e6394a9e8..27f19c1b0 100644 --- a/tests/merge/conflict_data.h +++ b/tests/merge/conflict_data.h @@ -70,22 +70,22 @@ "This is a mighty fine recipe!\n" \ ">>>>>>> branchF-2\n" -#define CONFLICTING_RECURSIVE_H1_TO_H2_WITH_DIFF3 \ +#define CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3 \ "VEAL SOUP.\n" \ "\n" \ "<<<<<<< HEAD\n" \ - "put into a pot three quarts of water, three onions cut small, one\n" \ - "||||||| merged common ancestors\n" \ - "<<<<<<< Temporary merge branch 1\n" \ - "Put into a pot three quarts of water, THREE ONIONS CUT SMALL, one\n" \ - "||||||| merged common ancestors\n" \ - "Put into a pot three quarts of water, three onions cut small, one\n" \ - "=======\n" \ - "PUT INTO A POT three quarts of water, three onions cut small, one\n" \ - ">>>>>>> Temporary merge branch 2\n" \ - "=======\n" \ "Put Into A Pot Three Quarts of Water, Three Onions Cut Small, One\n" \ - ">>>>>>> branchH-2\n" \ + "||||||| merged common ancestors\n" \ + "<<<<<<<<< Temporary merge branch 1\n" \ + "PUT INTO A POT three quarts of water, three onions cut small, one\n" \ + "||||||||| merged common ancestors\n" \ + "Put into a pot three quarts of water, three onions cut small, one\n" \ + "=========\n" \ + "Put into a pot three quarts of water, THREE ONIONS CUT SMALL, one\n" \ + ">>>>>>>>> Temporary merge branch 2\n" \ + "=======\n" \ + "put into a pot three quarts of water, three onions cut small, one\n" \ + ">>>>>>> branchH-1\n" \ "spoonful of black pepper pounded, and two of salt, with two or three\n" \ "slices of lean ham; let it boil steadily two hours; skim it\n" \ "occasionally, then put into it a shin of veal, let it boil two hours\n" \ diff --git a/tests/merge/files.c b/tests/merge/files.c index daa73fada..6f5a1fd9c 100644 --- a/tests/merge/files.c +++ b/tests/merge/files.c @@ -377,3 +377,51 @@ void test_merge_files__handles_binaries_when_favored(void) git_merge_file_result_free(&result); } + +void test_merge_files__crlf_conflict_markers_for_crlf_files(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + const char *expected = + "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" + "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; + size_t expected_len = strlen(expected); + + const char *expected_diff3 = + "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" + "||||||| file.txt\r\nThis file has\r\nCRLF line endings.\r\n" + "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; + size_t expected_diff3_len = strlen(expected_diff3); + + ancestor.ptr = "This file has\r\nCRLF line endings.\r\n"; + ancestor.size = 35; + ancestor.path = "file.txt"; + ancestor.mode = 0100644; + + ours.ptr = "This file\r\ndoes, too.\r\n"; + ours.size = 23; + ours.path = "file.txt"; + ours.mode = 0100644; + + theirs.ptr = "And so does\r\nthis one.\r\n"; + theirs.size = 24; + theirs.path = "file.txt"; + theirs.mode = 0100644; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_len, result.len); + cl_assert(memcmp(expected, result.ptr, expected_len) == 0); + git_merge_file_result_free(&result); + + opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_diff3_len, result.len); + cl_assert(memcmp(expected_diff3, result.ptr, expected_len) == 0); + git_merge_file_result_free(&result); +} diff --git a/tests/merge/trees/recursive.c b/tests/merge/trees/recursive.c index c5b129bf8..71f5af150 100644 --- a/tests/merge/trees/recursive.c +++ b/tests/merge/trees/recursive.c @@ -312,7 +312,7 @@ void test_merge_trees_recursive__conflicting_merge_base(void) { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "3a66812fed1e03ea4a6a7ee28d8a57aec1ca6537", 1, "veal.txt" }, + { 0100644, "cfc01b0976122eae42a82064440bbf534eddd7a0", 1, "veal.txt" }, { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, }; @@ -339,14 +339,14 @@ void test_merge_trees_recursive__conflicting_merge_base_with_diff3(void) { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "cd17a91513f3aee9e44114d1ede67932dd41d2fc", 1, "veal.txt" }, - { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, - { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, + { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, }; opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; - cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts)); + cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-2", "branchH-1", &opts)); cl_assert(merge_test_index(index, merge_index_entries, 8)); @@ -392,19 +392,67 @@ void test_merge_trees_recursive__recursionlimit(void) { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "ce7e553c6feb6e5f3bd67e3c3be04182fe3094b4", 1, "gravy.txt" }, - { 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" }, - { 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + { 0100644, "53217e8ac3f52bccf7603b8fff0ed0f4817f9bb7", 1, "veal.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, + { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, }; opts.recursion_limit = 1; - cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); cl_assert(merge_test_index(index, merge_index_entries, 8)); git_index_free(index); } +/* There are multiple levels of criss-cross merges. This ensures + * that the virtual merge base parents are compared in the same + * order as git. If the base parents are created in the order as + * git does, then the file `targetfile.txt` is automerged. If not, + * `targetfile.txt` will be in conflict due to the virtual merge + * base. + */ +void test_merge_trees_recursive__merge_base_for_virtual_commit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "1bde1883de4977ea3e664b315da951d1f614c3b1", 0, "targetfile.txt" }, + { 0100644, "b7de2b52ba055688061355fad1599a5d214ce8f8", 1, "version.txt" }, + { 0100644, "358efd6f589384fa8baf92234db9c7899a53916e", 2, "version.txt" }, + { 0100644, "a664873b1c0b9a1ed300f8644dde536fdaa3a34f", 3, "version.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchJ-1", "branchJ-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); +} + +/* This test is the same as above, but the graph is constructed such + * that the 1st-recursion merge bases of the two heads are + * in a different order. + */ +void test_merge_trees_recursive__merge_base_for_virtual_commit_2(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "4a06b258fed8a4d15967ec4253ae7366b70f727d", 0, "targetfile.txt" }, + { 0100644, "b6bd0f9952f396e757d3f91e08c59a7e91707201", 1, "version.txt" }, + { 0100644, "f0856993e005c0d8ed2dc7cdc222cc1d89fb3c77", 2, "version.txt" }, + { 0100644, "2cba583804a4a6fad1baf97c959be447238d1489", 3, "version.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchK-1", "branchK-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); +} diff --git a/tests/merge/workdir/recursive.c b/tests/merge/workdir/recursive.c index 795126255..d47a0c50b 100644 --- a/tests/merge/workdir/recursive.c +++ b/tests/merge/workdir/recursive.c @@ -44,7 +44,7 @@ void test_merge_workdir_recursive__writes_conflict_with_virtual_base(void) cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); cl_assert_equal_s(CONFLICTING_RECURSIVE_F1_TO_F2, conflicting_buf.ptr); - + git_index_free(index); git_buf_free(&conflicting_buf); } @@ -62,22 +62,22 @@ void test_merge_workdir_recursive__conflicting_merge_base_with_diff3(void) { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "cd17a91513f3aee9e44114d1ede67932dd41d2fc", 1, "veal.txt" }, - { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, - { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, + { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, }; opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; checkout_opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchH-1", GIT_REFS_HEADS_DIR "branchH-2", &opts, &checkout_opts)); + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchH-2", GIT_REFS_HEADS_DIR "branchH-1", &opts, &checkout_opts)); cl_git_pass(git_repository_index(&index, repo)); cl_assert(merge_test_index(index, merge_index_entries, 8)); cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); - cl_assert_equal_s(CONFLICTING_RECURSIVE_H1_TO_H2_WITH_DIFF3, conflicting_buf.ptr); + cl_assert_equal_s(CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3, conflicting_buf.ptr); git_index_free(index); git_buf_free(&conflicting_buf); diff --git a/tests/merge/workdir/submodules.c b/tests/merge/workdir/submodules.c index 7c18c2ffb..c4cc188a8 100644 --- a/tests/merge/workdir/submodules.c +++ b/tests/merge/workdir/submodules.c @@ -12,6 +12,7 @@ static git_repository *repo; #define SUBMODULE_MAIN_BRANCH "submodules" #define SUBMODULE_OTHER_BRANCH "submodules-branch" #define SUBMODULE_OTHER2_BRANCH "submodules-branch2" +#define SUBMODULE_DELETE_BRANCH "delete-submodule" #define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" @@ -93,3 +94,38 @@ void test_merge_workdir_submodules__take_changed(void) git_reference_free(their_ref); git_reference_free(our_ref); } + + +void test_merge_workdir_submodules__update_delete_conflict(void) +{ + git_reference *our_ref, *their_ref; + git_commit *our_commit; + git_annotated_commit *their_head; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, ".gitmodules" }, + { 0100644, "5887a5e516c53bd58efb0f02ec6aa031b6fe9ad7", 0, "file1.txt" }, + { 0100644, "4218670ab81cc219a9f94befb5c5dad90ec52648", 0, "file2.txt" }, + { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule"}, + { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 3, "submodule" }, + }; + + cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_DELETE_BRANCH)); + cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); + cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 5)); + + git_index_free(index); + git_annotated_commit_free(their_head); + git_commit_free(our_commit); + git_reference_free(their_ref); + git_reference_free(our_ref); +} diff --git a/tests/message/trailer.c b/tests/message/trailer.c new file mode 100644 index 000000000..9cc83de72 --- /dev/null +++ b/tests/message/trailer.c @@ -0,0 +1,165 @@ +#include "clar_libgit2.h" +#include "message.h" + +static void assert_trailers(const char *message, git_message_trailer *trailers) +{ + git_message_trailer_array arr; + size_t i; + + int rc = git_message_trailers(&arr, message); + + cl_assert_equal_i(0, rc); + + for(i=0; i= 0; ++i) { + cl_git_pass(git_note_commit_read(¬e, _repo, notes_commits[1], &annotated_id)); + cl_assert_equal_s(git_note_message(note), note_message[i]); + git_note_free(note); + } + + cl_assert_equal_i(GIT_ITEROVER, err); + cl_assert_equal_i(2, i); + + git_note_iterator_free(iter); + git_commit_free(notes_commits[0]); + git_commit_free(notes_commits[1]); +} diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index a9decf9c1..a1ee03d6d 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -495,6 +495,7 @@ static void test_inserting_submodule(void) git_treebuilder *bld; git_oid sm_id; + cl_git_pass(git_oid_fromstr(&sm_id, "da39a3ee5e6b4b0d3255bfef95601890afd80709")); cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); cl_git_pass(git_treebuilder_insert(NULL, bld, "sm", &sm_id, GIT_FILEMODE_COMMIT)); git_treebuilder_free(bld); @@ -512,3 +513,14 @@ void test_object_tree_write__object_validity(void) test_inserting_submodule(); } +void test_object_tree_write__invalid_null_oid(void) +{ + git_treebuilder *bld; + git_oid null_oid = {{0}}; + + cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); + cl_git_fail(git_treebuilder_insert(NULL, bld, "null_oid_file", &null_oid, GIT_FILEMODE_BLOB)); + cl_assert(giterr_last() && strstr(giterr_last()->message, "null OID") != NULL); + + git_treebuilder_free(bld); +} diff --git a/tests/odb/backend/mempack.c b/tests/odb/backend/mempack.c new file mode 100644 index 000000000..624f0e604 --- /dev/null +++ b/tests/odb/backend/mempack.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" +#include "git2/sys/mempack.h" + +static git_odb *_odb; +static git_oid _oid; +static git_odb_object *_obj; +static git_repository *_repo; + +void test_odb_backend_mempack__initialize(void) +{ + git_odb_backend *backend; + + cl_git_pass(git_mempack_new(&backend)); + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_add_backend(_odb, backend, 10)); + cl_git_pass(git_repository_wrap_odb(&_repo, _odb)); +} + +void test_odb_backend_mempack__cleanup(void) +{ + git_odb_object_free(_obj); + git_odb_free(_odb); + git_repository_free(_repo); +} + +void test_odb_backend_mempack__write_succeeds(void) +{ + const char *data = "data"; + cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJ_BLOB)); + cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_mempack__read_of_missing_object_fails(void) +{ + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_mempack__exists_of_missing_object_fails(void) +{ + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_assert(git_odb_exists(_odb, &_oid) == 0); +} + +void test_odb_backend_mempack__exists_with_existing_objects_succeeds(void) +{ + const char *data = "data"; + cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJ_BLOB)); + cl_assert(git_odb_exists(_odb, &_oid) == 1); +} + +void test_odb_backend_mempack__blob_create_frombuffer_succeeds(void) +{ + const char *data = "data"; + + cl_git_pass(git_blob_create_frombuffer(&_oid, _repo, data, strlen(data) + 1)); + cl_assert(git_odb_exists(_odb, &_oid) == 1); +} diff --git a/tests/odb/backend/simple.c b/tests/odb/backend/simple.c index c0fcd403b..f4d29cc76 100644 --- a/tests/odb/backend/simple.c +++ b/tests/odb/backend/simple.c @@ -230,3 +230,21 @@ void test_odb_backend_simple__exists_with_highly_ambiguous_prefix(void) cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 40)); cl_assert(git_oid_equal(&found, &_oid)); } + +void test_odb_backend_simple__null_oid_is_ignored(void) +{ + const fake_object objs[] = { + { "0000000000000000000000000000000000000000", "null oid content" }, + { NULL, NULL } + }; + git_oid null_oid = {{0}}; + git_odb_object *obj; + + setup_backend(objs); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_assert(!git_odb_exists(_odb, &null_oid)); + + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&obj, _odb, &null_oid)); + cl_assert(giterr_last() && strstr(giterr_last()->message, "null OID")); +} diff --git a/tests/odb/largefiles.c b/tests/odb/largefiles.c new file mode 100644 index 000000000..a5b982e14 --- /dev/null +++ b/tests/odb/largefiles.c @@ -0,0 +1,189 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" +#include "hash.h" +#include "odb.h" + +#define LARGEFILE_SIZE 5368709122 + +static git_repository *repo; +static git_odb *odb; + +void test_odb_largefiles__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); +} + +void test_odb_largefiles__cleanup(void) +{ + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +static void writefile(git_oid *oid) +{ + static git_odb_stream *stream; + git_buf buf = GIT_BUF_INIT; + size_t i; + + for (i = 0; i < 3041; i++) + cl_git_pass(git_buf_puts(&buf, "Hello, world.\n")); + + cl_git_pass(git_odb_open_wstream(&stream, odb, LARGEFILE_SIZE, GIT_OBJ_BLOB)); + for (i = 0; i < 126103; i++) + cl_git_pass(git_odb_stream_write(stream, buf.ptr, buf.size)); + + cl_git_pass(git_odb_stream_finalize_write(oid, stream)); + + git_odb_stream_free(stream); + git_buf_free(&buf); +} + +void test_odb_largefiles__write_from_memory(void) +{ + git_oid expected, oid; + git_buf buf = GIT_BUF_INIT; + size_t i; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + for (i = 0; i < (3041*126103); i++) + cl_git_pass(git_buf_puts(&buf, "Hello, world.\n")); + + git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); + cl_git_pass(git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)); + + cl_assert_equal_oid(&expected, &oid); +} + +void test_odb_largefiles__streamwrite(void) +{ + git_oid expected, oid; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); + writefile(&oid); + + cl_assert_equal_oid(&expected, &oid); +} + +void test_odb_largefiles__streamread(void) +{ + git_oid oid, read_oid; + git_odb_stream *stream; + char buf[10240]; + char hdr[64]; + size_t len, hdr_len, total = 0; + git_hash_ctx hash; + git_otype type; + int ret; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + + cl_git_pass(git_odb_open_rstream(&stream, &len, &type, odb, &oid)); + + cl_assert_equal_sz(LARGEFILE_SIZE, len); + cl_assert_equal_i(GIT_OBJ_BLOB, type); + + cl_git_pass(git_hash_ctx_init(&hash)); + cl_git_pass(git_odb__format_object_header(&hdr_len, hdr, sizeof(hdr), len, type)); + + cl_git_pass(git_hash_update(&hash, hdr, hdr_len)); + + while ((ret = git_odb_stream_read(stream, buf, 10240)) > 0) { + cl_git_pass(git_hash_update(&hash, buf, ret)); + total += ret; + } + + cl_assert_equal_sz(LARGEFILE_SIZE, total); + + git_hash_final(&read_oid, &hash); + + cl_assert_equal_oid(&oid, &read_oid); + + git_hash_ctx_cleanup(&hash); + git_odb_stream_free(stream); +} + +void test_odb_largefiles__read_into_memory(void) +{ + git_oid oid; + git_odb_object *obj; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_pass(git_odb_read(&obj, odb, &oid)); + + git_odb_object_free(obj); +} + +void test_odb_largefiles__read_into_memory_rejected_on_32bit(void) +{ + git_oid oid; + git_odb_object *obj = NULL; + +#ifdef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_fail(git_odb_read(&obj, odb, &oid)); + + git_odb_object_free(obj); +} + +void test_odb_largefiles__read_header(void) +{ + git_oid oid; + size_t len; + git_otype type; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_pass(git_odb_read_header(&len, &type, odb, &oid)); + + cl_assert_equal_sz(LARGEFILE_SIZE, len); + cl_assert_equal_i(GIT_OBJ_BLOB, type); +} diff --git a/tests/odb/loose.c b/tests/odb/loose.c index 2e24d6723..83d080729 100644 --- a/tests/odb/loose.c +++ b/tests/odb/loose.c @@ -55,6 +55,63 @@ static void test_read_object(object_data *data) git_odb_free(odb); } +static void test_read_header(object_data *data) +{ + git_oid id; + git_odb *odb; + size_t len; + git_otype type; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_read_header(&len, &type, odb, &id)); + + cl_assert_equal_sz(data->dlen, len); + cl_assert_equal_i(git_object_string2type(data->type), type); + + git_odb_free(odb); +} + +static void test_readstream_object(object_data *data, size_t blocksize) +{ + git_oid id; + git_odb *odb; + git_odb_stream *stream; + git_rawobj tmp; + char buf[2048], *ptr = buf; + size_t remain; + int ret; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_open_rstream(&stream, &tmp.len, &tmp.type, odb, &id)); + + remain = tmp.len; + + while (remain) { + cl_assert((ret = git_odb_stream_read(stream, ptr, blocksize)) >= 0); + if (ret == 0) + break; + + cl_assert(remain >= (size_t)ret); + remain -= ret; + ptr += ret; + } + + cl_assert(remain == 0); + + tmp.data = buf; + + cmp_objects(&tmp, data); + + git_odb_stream_free(stream); + git_odb_free(odb); +} + void test_odb_loose__initialize(void) { p_fsync__cnt = 0; @@ -103,6 +160,33 @@ void test_odb_loose__simple_reads(void) test_read_object(&some); } +void test_odb_loose__streaming_reads(void) +{ + size_t blocksizes[] = { 1, 2, 4, 16, 99, 1024, 123456789 }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(blocksizes); i++) { + test_readstream_object(&commit, blocksizes[i]); + test_readstream_object(&tree, blocksizes[i]); + test_readstream_object(&tag, blocksizes[i]); + test_readstream_object(&zero, blocksizes[i]); + test_readstream_object(&one, blocksizes[i]); + test_readstream_object(&two, blocksizes[i]); + test_readstream_object(&some, blocksizes[i]); + } +} + +void test_odb_loose__read_header(void) +{ + test_read_header(&commit); + test_read_header(&tree); + test_read_header(&tag); + test_read_header(&zero); + test_read_header(&one); + test_read_header(&two); + test_read_header(&some); +} + void test_write_object_permission( mode_t dir_mode, mode_t file_mode, mode_t expected_dir_mode, mode_t expected_file_mode) diff --git a/tests/online/clone.c b/tests/online/clone.c index 04fd22d45..27b7b9661 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -263,8 +263,8 @@ static int cred_failure_cb( void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) { - if (!_remote_url || !_remote_user) - clar__skip(); + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); g_options.fetch_opts.callbacks.credentials = cred_failure_cb; @@ -293,8 +293,8 @@ void test_online_clone__cred_callback_called_again_on_auth_failure(void) { size_t counter = 0; - if (!_remote_url || !_remote_user) - clar__skip(); + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; g_options.fetch_opts.callbacks.payload = &counter; @@ -348,7 +348,7 @@ void test_online_clone__credentials(void) void test_online_clone__bitbucket_style(void) { git_cred_userpass_payload user_pass = { - "libgit2", "libgit2" + "libgit3", "libgit3" }; g_options.fetch_opts.callbacks.credentials = git_cred_userpass; @@ -357,15 +357,45 @@ void test_online_clone__bitbucket_style(void) cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; cl_fixture_cleanup("./foo"); +} - /* User and pass from URL */ - user_pass.password = "wrong"; +void test_online_clone__bitbucket_uses_creds_in_url(void) +{ + git_cred_userpass_payload user_pass = { + "libgit2", "wrong" + }; + + g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + /* + * Correct user and pass are in the URL; the (incorrect) creds in + * the `git_cred_userpass_payload` should be ignored. + */ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; cl_fixture_cleanup("./foo"); +} - /* Wrong password in URL, fall back to user_pass */ - user_pass.password = "libgit2"; +void test_online_clone__bitbucket_falls_back_to_specified_creds(void) +{ + git_cred_userpass_payload user_pass = { + "libgit2", "libgit2" + }; + + g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + /* + * TODO: as of March 2018, bitbucket sporadically fails with + * 403s instead of replying with a 401 - but only sometimes. + */ + cl_skip(); + + /* + * Incorrect user and pass are in the URL; the (correct) creds in + * the `git_cred_userpass_payload` should be used as a fallback. + */ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; cl_fixture_cleanup("./foo"); @@ -547,7 +577,7 @@ void test_online_clone__ssh_cert(void) if (!_remote_ssh_fingerprint) cl_skip(); - cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, "ssh://localhost/foo", "./foo", &g_options)); + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); } static char *read_key_file(const char *path) @@ -677,25 +707,37 @@ static int proxy_creds(git_cred **out, const char *url, const char *username, un void test_online_clone__proxy_credentials_request(void) { + git_buf url = GIT_BUF_INIT; + if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass) cl_skip(); + cl_git_pass(git_buf_printf(&url, "http://%s/", _remote_proxy_url)); + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + g_options.fetch_opts.proxy_opts.url = url.ptr; g_options.fetch_opts.proxy_opts.credentials = proxy_creds; called_proxy_creds = 0; cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); cl_assert(called_proxy_creds); + + git_buf_free(&url); } void test_online_clone__proxy_credentials_in_url(void) { - if (!_remote_proxy_url) + git_buf url = GIT_BUF_INIT; + + if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass) cl_skip(); + cl_git_pass(git_buf_printf(&url, "http://%s:%s@%s/", _remote_proxy_user, _remote_proxy_pass, _remote_proxy_url)); + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + g_options.fetch_opts.proxy_opts.url = url.ptr; called_proxy_creds = 0; cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); cl_assert(called_proxy_creds == 0); + + git_buf_free(&url); } diff --git a/tests/online/remotes.c b/tests/online/remotes.c index a86f2d9ae..d79eb1f59 100644 --- a/tests/online/remotes.c +++ b/tests/online/remotes.c @@ -1,12 +1,13 @@ #include "clar_libgit2.h" -static const char *refspec = "refs/heads/first-merge:refs/remotes/origin/first-merge"; +#define URL "git://github.com/libgit2/TestGitRepository" +#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge" static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) { GIT_UNUSED(payload); - cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, refspec)); + cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, REFSPEC)); return 0; } @@ -22,7 +23,7 @@ void test_online_remotes__single_branch(void) opts.remote_cb = remote_single_branch; opts.checkout_branch = "first-merge"; - cl_git_pass(git_clone(&repo, "git://github.com/libgit2/TestGitRepository", "./single-branch", &opts)); + cl_git_pass(git_clone(&repo, URL, "./single-branch", &opts)); cl_git_pass(git_reference_list(&refs, repo)); for (i = 0; i < refs.count; i++) { @@ -37,7 +38,7 @@ void test_online_remotes__single_branch(void) cl_git_pass(git_remote_get_fetch_refspecs(&refs, remote)); cl_assert_equal_i(1, refs.count); - cl_assert_equal_s(refspec, refs.strings[0]); + cl_assert_equal_s(REFSPEC, refs.strings[0]); git_strarray_free(&refs); git_remote_free(remote); @@ -51,5 +52,76 @@ void test_online_remotes__restricted_refspecs(void) opts.remote_cb = remote_single_branch; - cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, "git://github.com/libgit2/TestGitRepository", "./restrict-refspec", &opts)); + cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, URL, "./restrict-refspec", &opts)); +} + +void test_online_remotes__detached_remote_fails_downloading(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_download(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_fails_uploading(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_upload(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_fails_pushing(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_push(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_succeeds_ls(void) +{ + const char *refs[] = { + "HEAD", + "refs/heads/first-merge", + "refs/heads/master", + "refs/heads/no-parent", + "refs/tags/annotated_tag", + "refs/tags/annotated_tag^{}", + "refs/tags/blob", + "refs/tags/commit_tree", + "refs/tags/nearly-dangling", + }; + const git_remote_head **heads; + git_remote *remote; + size_t i, j, n; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_ls(&heads, &n, remote)); + + cl_assert_equal_sz(n, 9); + for (i = 0; i < n; i++) { + char found = false; + + for (j = 0; j < ARRAY_SIZE(refs); j++) { + if (!strcmp(heads[i]->name, refs[j])) { + found = true; + break; + } + } + + cl_assert_(found, heads[i]->name); + } + + git_remote_free(remote); } diff --git a/tests/pack/indexer.c b/tests/pack/indexer.c index c73d3974e..f3c2204bd 100644 --- a/tests/pack/indexer.c +++ b/tests/pack/indexer.c @@ -40,6 +40,40 @@ static const unsigned char thin_pack[] = { }; static const unsigned int thin_pack_len = 78; +/* + * Packfile with one object. It references an object which is not in the + * packfile and has a corrupt length (states the deltified stream is 1 byte + * long, where it is actually 6). + */ +static const unsigned char corrupt_thin_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + 0x71, 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, + 0x10, 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, + 0x62, 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x07, + 0x67, 0x03, 0xc5, 0x40, 0x99, 0x49, 0xb1, 0x3b, 0x7d, 0xae, 0x9b, 0x0e, + 0xdd, 0xde, 0xc6, 0x76, 0x43, 0x24, 0x64 +}; +static const unsigned int corrupt_thin_pack_len = 67; + +/* + * Packfile with a missing trailer. + */ +static const unsigned char missing_trailer_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0xf4, 0x3b, +}; +static const unsigned int missing_trailer_pack_len = 12; + +/* + * Packfile that causes the packfile stream to open in a way in which it leaks + * the stream reader. + */ +static const unsigned char leaky_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, + 0xf4, 0xbd, 0x51, 0x51, 0x51, 0x51, 0x51, 0x72, 0x65, 0x41, 0x4b, 0x63, + 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0xbd, 0x41, 0x4b +}; +static const unsigned int leaky_pack_len = 33; + static const unsigned char base_obj[] = { 07, 076 }; static const unsigned int base_obj_len = 2; @@ -60,6 +94,38 @@ void test_pack_indexer__out_of_order(void) git_indexer_free(idx); } +void test_pack_indexer__missing_trailer(void) +{ + git_indexer *idx = 0; + git_transfer_progress stats = { 0 }; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, missing_trailer_pack, missing_trailer_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(giterr_last() != NULL); + cl_assert_equal_i(giterr_last()->klass, GITERR_INDEXER); + + git_indexer_free(idx); +} + +void test_pack_indexer__leaky(void) +{ + git_indexer *idx = 0; + git_transfer_progress stats = { 0 }; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, leaky_pack, leaky_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(giterr_last() != NULL); + cl_assert_equal_i(giterr_last()->klass, GITERR_INDEXER); + + git_indexer_free(idx); +} + void test_pack_indexer__fix_thin(void) { git_indexer *idx = NULL; @@ -126,6 +192,35 @@ void test_pack_indexer__fix_thin(void) } } +void test_pack_indexer__corrupt_length(void) +{ + git_indexer *idx = NULL; + git_transfer_progress stats = { 0 }; + git_repository *repo; + git_odb *odb; + git_oid id, should_id; + + cl_git_pass(git_repository_init(&repo, "thin.git", true)); + cl_git_pass(git_repository_odb(&odb, repo)); + + /* Store the missing base into your ODB so the indexer can fix the pack */ + cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB)); + git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); + cl_assert_equal_oid(&should_id, &id); + + cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, corrupt_thin_pack, corrupt_thin_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(giterr_last() != NULL); + cl_assert_equal_i(giterr_last()->klass, GITERR_ZLIB); + + git_indexer_free(idx); + git_odb_free(odb); + git_repository_free(repo); +} + static int find_tmp_file_recurs(void *opaque, git_buf *path) { int error = 0; diff --git a/tests/patch/parse.c b/tests/patch/parse.c index 8350ac2dd..a40ad7b23 100644 --- a/tests/patch/parse.c +++ b/tests/patch/parse.c @@ -102,3 +102,9 @@ void test_patch_parse__invalid_patches_fails(void) strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); } +void test_patch_parse__files_with_whitespaces_succeeds(void) +{ + git_patch *patch; + cl_git_pass(git_patch_from_buffer(&patch, PATCH_NAME_WHITESPACE, strlen(PATCH_NAME_WHITESPACE), NULL)); + git_patch_free(patch); +} diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h index 6ec554690..e838e6089 100644 --- a/tests/patch/patch_common.h +++ b/tests/patch/patch_common.h @@ -253,7 +253,66 @@ "@@ -9,0 +10 @@ below it!\n" \ "+insert at end\n" -/* An insertion at the beginning and end of file (and the resultant patch) */ +#define PATCH_SIMPLE_COMMIT \ + "commit 15e119375018fba121cf58e02a9f17fe22df0df8\n" \ + "Author: Edward Thomson \n" \ + "Date: Wed Jun 14 13:31:20 2017 +0200\n" \ + "\n" \ + " CHANGELOG: document git_filter_init and GIT_FILTER_INIT\n" \ + "\n" \ + "diff --git a/CHANGELOG.md b/CHANGELOG.md\n" \ + "index 1b9e0c90a..24ecba426 100644\n" \ + "--- a/CHANGELOG.md\n" \ + "+++ b/CHANGELOG.md\n" \ + "@@ -96,6 +96,9 @@ v0.26\n" \ + " * `git_transport_smart_proxy_options()' enables you to get the proxy options for\n" \ + " smart transports.\n" \ + "\n" \ + "+* The `GIT_FILTER_INIT` macro and the `git_filter_init` function are provided\n" \ + "+ to initialize a `git_filter` structure.\n" \ + "+\n" \ + " ### Breaking API changes\n" \ + "\n" \ + " * `clone_checkout_strategy` has been removed from\n" + +#define PATCH_MULTIPLE_HUNKS \ + "diff --git a/x b/x\n" \ + "index 0719398..fa0350c 100644\n" \ + "--- a/x\n" \ + "+++ b/x\n" \ + "@@ -1,5 +1,4 @@\n" \ + " 1\n" \ + "-2\n" \ + " 3\n" \ + " 4\n" \ + " 5\n" \ + "@@ -7,3 +6,4 @@\n" \ + " 7\n" \ + " 8\n" \ + " 9\n" \ + "+10\n" + +#define PATCH_MULTIPLE_FILES \ + "diff --git a/x b/x\n" \ + "index 8a1218a..7059ba5 100644\n" \ + "--- a/x\n" \ + "+++ b/x\n" \ + "@@ -1,5 +1,4 @@\n" \ + " 1\n" \ + " 2\n" \ + "-3\n" \ + " 4\n" \ + " 5\n" \ + "diff --git a/y b/y\n" \ + "index e006065..9405325 100644\n" \ + "--- a/y\n" \ + "+++ b/y\n" \ + "@@ -1,4 +1,5 @@\n" \ + " a\n" \ + " b\n" \ + "+c\n" \ + " d\n" \ + " e\n" #define FILE_PREPEND_AND_APPEND \ "first and\n" \ @@ -516,6 +575,16 @@ "+added line with no nl\n" \ "\\ No newline at end of file\n" +#define PATCH_NAME_WHITESPACE \ + "diff --git a/file with spaces.txt b/file with spaces.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file with spaces.txt\n" \ + "+++ b/file with spaces.txt\n" \ + "@@ -0,3 +0,2 @@\n" \ + " and this\n" \ + "-is additional context\n" \ + " below it!\n" \ + #define PATCH_CORRUPT_GIT_HEADER \ "diff --git a/file.txt\n" \ "index 9432026..0f39b9a 100644\n" \ diff --git a/tests/perf/merge.c b/tests/perf/merge.c index b2ef082eb..721902d63 100644 --- a/tests/perf/merge.c +++ b/tests/perf/merge.c @@ -25,20 +25,7 @@ #define ID_BRANCH_A "d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b" #define ID_BRANCH_B "1ce9ea3ba9b4fa666602d52a5281d41a482cc58b" - -void test_perf_merge__initialize(void) -{ -} - -void test_perf_merge__cleanup(void) -{ -} - void test_perf_merge__m1(void) { -#if 1 - cl_skip(); -#else perf__do_merge(SRC_REPO, "m1", ID_BRANCH_A, ID_BRANCH_B); -#endif } diff --git a/tests/precompiled.c b/tests/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/tests/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/tests/precompiled.h b/tests/precompiled.h new file mode 100644 index 000000000..ea53a60e9 --- /dev/null +++ b/tests/precompiled.h @@ -0,0 +1,4 @@ +#include "common.h" +#include "git2.h" +#include "clar.h" +#include "clar_libgit2.h" diff --git a/tests/rebase/submodule.c b/tests/rebase/submodule.c index 7a38ab89f..8ae78ce5b 100644 --- a/tests/rebase/submodule.c +++ b/tests/rebase/submodule.c @@ -3,6 +3,7 @@ #include "git2/rebase.h" #include "posix.h" #include "signature.h" +#include "../submodule/submodule_helpers.h" #include @@ -12,9 +13,44 @@ static git_signature *signature; // Fixture setup and teardown void test_rebase_submodule__initialize(void) { + git_index *index; + git_oid tree_oid, commit_id; + git_tree *tree; + git_commit *parent; + git_object *obj; + git_reference *master_ref; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + repo = cl_git_sandbox_init("rebase-submodule"); cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); + + rewrite_gitmodules(git_repository_workdir(repo)); + + cl_git_pass(git_submodule_set_url(repo, "my-submodule", git_repository_path(repo))); + + /* We have to commit the rewritten .gitmodules file */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, ".gitmodules")); + cl_git_pass(git_index_write_tree(&tree_oid, index)); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + + cl_git_pass(git_repository_head(&master_ref, repo)); + cl_git_pass(git_commit_lookup(&parent, repo, git_reference_target(master_ref))); + + cl_git_pass(git_commit_create_v(&commit_id, repo, git_reference_name(master_ref), signature, signature, NULL, "Fixup .gitmodules", tree, 1, parent)); + + /* And a final reset, for good measure */ + cl_git_pass(git_object_lookup(&obj, repo, &commit_id, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, obj, GIT_RESET_HARD, &opts)); + + git_index_free(index); + git_object_free(obj); + git_commit_free(parent); + git_reference_free(master_ref); + git_tree_free(tree); } void test_rebase_submodule__cleanup(void) @@ -31,7 +67,6 @@ void test_rebase_submodule__init_untracked(void) git_buf untracked_path = GIT_BUF_INIT; FILE *fp; git_submodule *submodule; - git_config *config; cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); @@ -39,12 +74,6 @@ void test_rebase_submodule__init_untracked(void) cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - git_repository_config(&config, repo); - - cl_git_pass(git_config_set_string(config, "submodule.my-submodule.url", git_repository_path(repo))); - - git_config_free(config); - cl_git_pass(git_submodule_lookup(&submodule, repo, "my-submodule")); cl_git_pass(git_submodule_update(submodule, 1, NULL)); diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c index 69488e6c7..a4db57446 100644 --- a/tests/refs/branches/create.c +++ b/tests/refs/branches/create.c @@ -116,51 +116,6 @@ void test_refs_branches_create__creating_a_branch_with_an_invalid_name_returns_E git_branch_create(&branch, repo, "inv@{id", target, 0)); } -void test_refs_branches_create__default_reflog_message(void) -{ - git_reflog *log; - git_buf buf = GIT_BUF_INIT; - const git_reflog_entry *entry; - git_annotated_commit *annotated; - git_signature *sig; - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "user.name", "Foo Bar")); - cl_git_pass(git_config_set_string(cfg, "user.email", "foo@example.com")); - git_config_free(cfg); - - cl_git_pass(git_signature_default(&sig, repo)); - - retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, false)); - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/" NEW_BRANCH_NAME)); - - entry = git_reflog_entry_byindex(log, 0); - cl_git_pass(git_buf_printf(&buf, "branch: Created from %s", git_oid_tostr_s(git_commit_id(target)))); - cl_assert_equal_s(git_buf_cstr(&buf), git_reflog_entry_message(entry)); - cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email); - - cl_git_pass(git_reference_remove(repo, "refs/heads/" NEW_BRANCH_NAME)); - git_reference_free(branch); - git_reflog_free(log); - git_buf_clear(&buf); - - cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "e90810b8df3")); - cl_git_pass(git_branch_create_from_annotated(&branch, repo, NEW_BRANCH_NAME, annotated, true)); - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/" NEW_BRANCH_NAME)); - - entry = git_reflog_entry_byindex(log, 0); - cl_git_pass(git_buf_printf(&buf, "branch: Created from e90810b8df3")); - cl_assert_equal_s(git_buf_cstr(&buf), git_reflog_entry_message(entry)); - cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email); - - git_annotated_commit_free(annotated); - git_buf_free(&buf); - git_reflog_free(log); - git_signature_free(sig); -} - static void assert_branch_matches_name( const char *expected, const char *lookup_as) { diff --git a/tests/refs/branches/move.c b/tests/refs/branches/move.c index bec39e18b..acd8661b8 100644 --- a/tests/refs/branches/move.c +++ b/tests/refs/branches/move.c @@ -195,41 +195,6 @@ void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD( git_reference_free(branch); } -void test_refs_branches_move__default_reflog_message(void) -{ - git_reference *branch; - git_reference *new_branch; - git_reflog *log; - const git_reflog_entry *entry; - git_signature *sig; - git_config *cfg; - git_oid id; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "user.name", "Foo Bar")); - cl_git_pass(git_config_set_string(cfg, "user.email", "foo@example.com")); - git_config_free(cfg); - - cl_git_pass(git_signature_default(&sig, repo)); - - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - git_oid_cpy(&id, git_reference_target(branch)); - cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); - - cl_git_pass(git_reflog_read(&log, repo, git_reference_name(new_branch))); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s("branch: renamed refs/heads/master to refs/heads/master2", - git_reflog_entry_message(entry)); - cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email); - cl_assert_equal_oid(&id, git_reflog_entry_id_old(entry)); - cl_assert_equal_oid(&id, git_reflog_entry_id_new(entry)); - - git_reference_free(branch); - git_reference_free(new_branch); - git_reflog_free(log); - git_signature_free(sig); -} - void test_refs_branches_move__can_move_with_unicode(void) { git_reference *original_ref, *new_ref; diff --git a/tests/refs/crashes.c b/tests/refs/crashes.c index 7a10411c8..228f479a0 100644 --- a/tests/refs/crashes.c +++ b/tests/refs/crashes.c @@ -6,7 +6,7 @@ void test_refs_crashes__double_free(void) git_reference *ref, *ref2; const char *REFNAME = "refs/heads/xxx"; - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + repo = cl_git_sandbox_init("testrepo.git"); cl_git_pass(git_reference_symbolic_create(&ref, repo, REFNAME, "refs/heads/master", 0, NULL)); cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME)); cl_git_pass(git_reference_delete(ref)); @@ -16,5 +16,5 @@ void test_refs_crashes__double_free(void) /* reference is gone from disk, so reloading it will fail */ cl_git_fail(git_reference_lookup(&ref2, repo, REFNAME)); - git_repository_free(repo); + cl_git_sandbox_cleanup(); } diff --git a/tests/refs/createwithlog.c b/tests/refs/createwithlog.c deleted file mode 100644 index 4f643635b..000000000 --- a/tests/refs/createwithlog.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; - -static git_repository *g_repo; - -void test_refs_createwithlog__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_createwithlog__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_createwithlog__creating_a_direct_reference_adds_a_reflog_entry(void) -{ - git_reference *reference; - git_oid id; - git_reflog *reflog; - const git_reflog_entry *entry; - - const char *name = "refs/heads/new-head"; - const char *message = "You've been logged, mate!"; - - git_oid_fromstr(&id, current_master_tip); - - cl_git_pass( - git_reference_create(&reference, g_repo, name, &id, 0, message)); - - cl_git_pass(git_reflog_read(&reflog, g_repo, name)); - cl_assert_equal_sz(1, git_reflog_entrycount(reflog)); - - entry = git_reflog_entry_byindex(reflog, 0); - cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); - cl_assert_equal_oid(&id, &entry->oid_cur); - cl_assert_equal_s(message, entry->msg); - - git_reflog_free(reflog); - git_reference_free(reference); -} diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c index c77451309..18e9d1d5b 100644 --- a/tests/refs/iterator.c +++ b/tests/refs/iterator.c @@ -6,12 +6,12 @@ static git_repository *repo; void test_refs_iterator__initialize(void) { - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + repo = cl_git_sandbox_init("testrepo.git"); } void test_refs_iterator__cleanup(void) { - git_repository_free(repo); + cl_git_sandbox_cleanup(); } static const char *refnames[] = { @@ -36,6 +36,36 @@ static const char *refnames[] = { "refs/tags/taggerless", "refs/tags/test", "refs/tags/wrapped_tag", + NULL +}; + +static const char *refnames_with_symlink[] = { + "refs/heads/br2", + "refs/heads/cannot-fetch", + "refs/heads/chomped", + "refs/heads/haacked", + "refs/heads/link/a", + "refs/heads/link/b", + "refs/heads/link/c", + "refs/heads/link/d", + "refs/heads/master", + "refs/heads/not-good", + "refs/heads/packed", + "refs/heads/packed-test", + "refs/heads/subtrees", + "refs/heads/test", + "refs/heads/track-local", + "refs/heads/trailing", + "refs/notes/fanout", + "refs/remotes/test/master", + "refs/tags/annotated_tag_to_blob", + "refs/tags/e90810b", + "refs/tags/hard_tag", + "refs/tags/point_to_blob", + "refs/tags/taggerless", + "refs/tags/test", + "refs/tags/wrapped_tag", + NULL }; static int refcmp_cb(const void *a, const void *b) @@ -46,21 +76,21 @@ static int refcmp_cb(const void *a, const void *b) return strcmp(refa->name, refb->name); } -static void assert_all_refnames_match(git_vector *output) +static void assert_all_refnames_match(const char **expected, git_vector *names) { size_t i; git_reference *ref; - cl_assert_equal_sz(output->length, ARRAY_SIZE(refnames)); + git_vector_sort(names); - git_vector_sort(output); - - git_vector_foreach(output, i, ref) { - cl_assert_equal_s(ref->name, refnames[i]); + git_vector_foreach(names, i, ref) { + cl_assert(expected[i] != NULL); + cl_assert_equal_s(expected[i], ref->name); git_reference_free(ref); } + cl_assert(expected[i] == NULL); - git_vector_free(output); + git_vector_free(names); } void test_refs_iterator__list(void) @@ -82,7 +112,7 @@ void test_refs_iterator__list(void) git_reference_iterator_free(iter); - assert_all_refnames_match(&output); + assert_all_refnames_match(refnames, &output); } void test_refs_iterator__empty(void) @@ -115,7 +145,29 @@ void test_refs_iterator__foreach(void) git_vector output; cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); - assert_all_refnames_match(&output); + assert_all_refnames_match(refnames, &output); +} + +void test_refs_iterator__foreach_through_symlink(void) +{ + git_vector output; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); + + cl_git_pass(p_mkdir("refs", 0777)); + cl_git_mkfile("refs/a", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/b", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/c", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/d", "1234567890123456789012345678901234567890"); + + cl_git_pass(p_symlink("../../../refs", "testrepo.git/refs/heads/link")); + + cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); + assert_all_refnames_match(refnames_with_symlink, &output); } static int refs_foreach_cancel_cb(git_reference *reference, void *payload) @@ -156,11 +208,11 @@ void test_refs_iterator__foreach_name(void) cl_git_pass( git_reference_foreach_name(repo, refs_foreach_name_cb, &output)); - cl_assert_equal_sz(output.length, ARRAY_SIZE(refnames)); git_vector_sort(&output); git_vector_foreach(&output, i, name) { - cl_assert_equal_s(name, refnames[i]); + cl_assert(refnames[i] != NULL); + cl_assert_equal_s(refnames[i], name); git__free(name); } @@ -194,7 +246,7 @@ void test_refs_iterator__concurrent_delete(void) const char *name; int error; - git_repository_free(repo); + cl_git_sandbox_cleanup(); repo = cl_git_sandbox_init("testrepo"); cl_git_pass(git_reference_iterator_new(&iter, repo)); @@ -215,7 +267,4 @@ void test_refs_iterator__concurrent_delete(void) cl_assert_equal_i(GIT_ITEROVER, error); cl_assert_equal_i(full_count, concurrent_count); - - cl_git_sandbox_cleanup(); - repo = NULL; } diff --git a/tests/refs/list.c b/tests/refs/list.c index f7ca3f707..97461fd69 100644 --- a/tests/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 17); + cl_assert_equal_i((int)ref_list.count, 18); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 17); + cl_assert_equal_i((int)ref_list.count, 18); git_strarray_free(&ref_list); } diff --git a/tests/refs/peel.c b/tests/refs/peel.c index 83f6109c0..09809e19e 100644 --- a/tests/refs/peel.c +++ b/tests/refs/peel.c @@ -117,3 +117,15 @@ void test_refs_peel__can_peel_fully_peeled_packed_refs(void) "0df1a5865c8abfc09f1f2182e6a31be550e99f07", GIT_OBJ_COMMIT); } + +void test_refs_peel__can_peel_fully_peeled_tag_to_tag(void) +{ + assert_peel_generic(g_peel_repo, + "refs/tags/tag-inside-tags", GIT_OBJ_TAG, + "c2596aa0151888587ec5c0187f261e63412d9e11", + GIT_OBJ_TAG); + assert_peel_generic(g_peel_repo, + "refs/foo/tag-outside-tags", GIT_OBJ_TAG, + "c2596aa0151888587ec5c0187f261e63412d9e11", + GIT_OBJ_TAG); +} diff --git a/tests/refs/reflog/messages.c b/tests/refs/reflog/messages.c new file mode 100644 index 000000000..a7a77666b --- /dev/null +++ b/tests/refs/reflog/messages.c @@ -0,0 +1,409 @@ +#include "clar_libgit2.h" + +#include "fileops.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "refs.h" +#include "reflog_helpers.h" + +static const char *g_email = "foo@example.com"; +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_refs_reflog_messages__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_set_ident(g_repo, "Foo Bar", g_email)); +} + +void test_refs_reflog_messages__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_messages__setting_head_updates_reflog(void) +{ + git_object *tag; + git_signature *sig; + git_annotated_commit *annotated; + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 4 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/unborn")); + cl_git_pass(git_revparse_single(&tag, g_repo, "tags/test")); + cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(tag))); /* 3 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 2 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/tags/test")); /* 1 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/remotes/test/master")); /* 0 */ + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 4, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from master to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 3, + NULL, "tags/test^{commit}", + "foo@example.com", + "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 2, + "tags/test^{commit}", "refs/heads/haacked", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 1, + "refs/heads/haacked", "tags/test^{commit}", + "foo@example.com", + "checkout: moving from haacked to test"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "tags/test^{commit}", "refs/remotes/test/master", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master"); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "haacked~0")); + cl_git_pass(git_repository_set_head_detached_from_annotated(g_repo, annotated)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0"); + + git_annotated_commit_free(annotated); + git_object_free(tag); + git_signature_free(sig); +} + +void test_refs_reflog_messages__setting_head_to_same_target_ignores_reflog(void) +{ + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); +} + +void test_refs_reflog_messages__detaching_writes_reflog(void) +{ + git_signature *sig; + git_oid id; + const char *msg; + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + msg = "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "e90810b8df3e80c413d903f631643c716887138d", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); + + git_signature_free(sig); +} + +void test_refs_reflog_messages__orphan_branch_does_not_count(void) +{ + git_oid id; + const char *msg; + + /* Have something known */ + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* Switching to an orphan branch does not write to the reflog */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* And coming back, we set the source to zero */ + msg = "checkout: moving from orphan to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "0000000000000000000000000000000000000000", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); +} + +void test_refs_reflog_messages__branch_birth(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref; + const char *msg; + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJ_TREE)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries, nentries_after); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); + + git_signature_free(sig); + git_tree_free(tree); + git_reference_free(ref); +} + +void test_refs_reflog_messages__commit_on_symbolic_ref_updates_head_reflog(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref1, *ref2; + const char *msg; + size_t nentries_head, nentries_master; + + nentries_head = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref1, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref1, GIT_OBJ_TREE)); + + nentries_master = reflog_entrycount(g_repo, "refs/heads/master"); + + msg = "message 1"; + cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, "refs/heads/master", "refs/heads/foo", 1, msg)); + + cl_assert_equal_i(0, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head + 1, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + git_signature_free(sig); + git_reference_free(ref1); + git_reference_free(ref2); + git_tree_free(tree); +} + +void test_refs_reflog_messages__show_merge_for_merge_commits(void) +{ + git_oid b1_oid; + git_oid b2_oid; + git_oid merge_commit_oid; + git_commit *b1_commit; + git_commit *b2_commit; + git_signature *s; + git_commit *parent_commits[2]; + git_tree *tree; + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_pass(git_reference_name_to_id(&b1_oid, g_repo, "HEAD")); + cl_git_pass(git_reference_name_to_id(&b2_oid, g_repo, "refs/heads/test")); + + cl_git_pass(git_commit_lookup(&b1_commit, g_repo, &b1_oid)); + cl_git_pass(git_commit_lookup(&b2_commit, g_repo, &b2_oid)); + + parent_commits[0] = b1_commit; + parent_commits[1] = b2_commit; + + cl_git_pass(git_commit_tree(&tree, b1_commit)); + + cl_git_pass(git_commit_create(&merge_commit_oid, + g_repo, "HEAD", s, s, NULL, + "Merge commit", tree, + 2, (const struct git_commit **) parent_commits)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, + git_oid_tostr_s(&merge_commit_oid), + NULL, "commit (merge): Merge commit"); + + git_tree_free(tree); + git_commit_free(b1_commit); + git_commit_free(b2_commit); + git_signature_free(s); +} + +void test_refs_reflog_messages__creating_a_direct_reference(void) +{ + git_reference *reference; + git_oid id; + git_reflog *reflog; + const git_reflog_entry *entry; + + const char *name = "refs/heads/new-head"; + const char *message = "You've been logged, mate!"; + + cl_git_pass(git_reference_name_to_id(&id, g_repo, "HEAD")); + + cl_git_pass(git_reference_create(&reference, g_repo, name, &id, 0, message)); + + cl_git_pass(git_reflog_read(&reflog, g_repo, name)); + cl_assert_equal_sz(1, git_reflog_entrycount(reflog)); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + cl_assert_equal_oid(&id, &entry->oid_cur); + cl_assert_equal_s(message, entry->msg); + + git_reflog_free(reflog); + git_reference_free(reference); +} + + +void test_refs_reflog_messages__renaming_ref(void) +{ + git_reference *ref, *new_ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_ref, ref, "refs/heads/renamed", false, + "message")); + + cl_reflog_check_entry(g_repo, git_reference_name(new_ref), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "foo@example.com", "message"); + + git_reference_free(ref); + git_reference_free(new_ref); +} + +void test_refs_reflog_messages__updating_a_direct_reference(void) +{ + git_reference *ref, *ref_out, *target_ref; + git_oid target_id; + const char *message = "You've been logged, mate!"; + + git_reference_name_to_id(&target_id, g_repo, "refs/heads/haacked"); + cl_git_pass(git_reference_lookup(&target_ref, g_repo, "refs/heads/haacked")); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + + cl_git_pass(git_reference_set_target(&ref_out, ref, &target_id, message)); + + cl_reflog_check_entry(g_repo, "refs/heads/master", 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, message); + + git_reference_free(target_ref); + git_reference_free(ref); + git_reference_free(ref_out); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_reflog_messages__creating_branches_default_messages(void) +{ + git_buf buf = GIT_BUF_INIT; + git_annotated_commit *annotated; + git_object *obj; + git_commit *target; + git_reference *branch1, *branch2; + + cl_git_pass(git_revparse_single(&obj, g_repo, "e90810b8df3")); + cl_git_pass(git_commit_lookup(&target, g_repo, git_object_id(obj))); + git_object_free(obj); + + cl_git_pass(git_branch_create(&branch1, g_repo, NEW_BRANCH_NAME, target, false)); + + cl_git_pass(git_buf_printf(&buf, "branch: Created from %s", git_oid_tostr_s(git_commit_id(target)))); + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, git_buf_cstr(&buf)); + + cl_git_pass(git_reference_remove(g_repo, "refs/heads/" NEW_BRANCH_NAME)); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "e90810b8df3")); + cl_git_pass(git_branch_create_from_annotated(&branch2, g_repo, NEW_BRANCH_NAME, annotated, true)); + + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, "branch: Created from e90810b8df3"); + + git_annotated_commit_free(annotated); + git_buf_free(&buf); + git_commit_free(target); + git_reference_free(branch1); + git_reference_free(branch2); +} + +void test_refs_reflog_messages__moving_branch_default_message(void) +{ + git_reference *branch; + git_reference *new_branch; + git_oid id; + + cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master")); + git_oid_cpy(&id, git_reference_target(branch)); + cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); + + cl_reflog_check_entry(g_repo, git_reference_name(new_branch), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + g_email, + "branch: renamed refs/heads/master to refs/heads/master2"); + + git_reference_free(branch); + git_reference_free(new_branch); +} + +void test_refs_reflog_messages__detaching_head_default_message(void) +{ + git_reference *ref; + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + cl_git_pass(git_repository_detach_head(g_repo)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "checkout: moving from master to a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_assert_equal_i(true, git_repository_head_detached(g_repo)); + + /* take the repo back to its original state */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", "refs/heads/master", + true, "REATTACH")); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "REATTACH"); + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + git_reference_free(ref); +} diff --git a/tests/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c index 252242152..bbcdbf91b 100644 --- a/tests/refs/reflog/reflog.c +++ b/tests/refs/reflog/reflog.c @@ -4,7 +4,6 @@ #include "git2/reflog.h" #include "reflog.h" -static const char *merge_reflog_message = "commit (merge): Merge commit"; static const char *new_ref = "refs/heads/test-reflog"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; #define commit_msg "commit: bla bla" @@ -448,45 +447,3 @@ void test_refs_reflog_reflog__logallrefupdates_nonbare_set_false(void) assert_no_reflog_update(); } - -void test_refs_reflog_reflog__show_merge_for_merge_commits(void) -{ - git_oid b1_oid; - git_oid b2_oid; - git_oid merge_commit_oid; - git_commit *b1_commit; - git_commit *b2_commit; - git_signature *s; - git_commit *parent_commits[2]; - git_tree *tree; - git_reflog *log; - const git_reflog_entry *entry; - - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - - cl_git_pass(git_reference_name_to_id(&b1_oid, g_repo, "HEAD")); - cl_git_pass(git_reference_name_to_id(&b2_oid, g_repo, "refs/heads/test")); - - cl_git_pass(git_commit_lookup(&b1_commit, g_repo, &b1_oid)); - cl_git_pass(git_commit_lookup(&b2_commit, g_repo, &b2_oid)); - - parent_commits[0] = b1_commit; - parent_commits[1] = b2_commit; - - cl_git_pass(git_commit_tree(&tree, b1_commit)); - - cl_git_pass(git_commit_create(&merge_commit_oid, - g_repo, "HEAD", s, s, NULL, - "Merge commit", tree, - 2, (const struct git_commit **) parent_commits)); - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s(merge_reflog_message, git_reflog_entry_message(entry)); - - git_reflog_free(log); - git_tree_free(tree); - git_commit_free(b1_commit); - git_commit_free(b2_commit); - git_signature_free(s); -} diff --git a/tests/refs/reflog/reflog_helpers.c b/tests/refs/reflog/reflog_helpers.c new file mode 100644 index 000000000..5e3673651 --- /dev/null +++ b/tests/refs/reflog/reflog_helpers.c @@ -0,0 +1,118 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "reflog.h" + +static int reflog_entry_tostr(git_buf *out, const git_reflog_entry *entry) +{ + char old_oid[GIT_OID_HEXSZ], new_oid[GIT_OID_HEXSZ]; + + assert(out && entry); + + git_oid_tostr((char *)&old_oid, GIT_OID_HEXSZ, git_reflog_entry_id_old(entry)); + git_oid_tostr((char *)&new_oid, GIT_OID_HEXSZ, git_reflog_entry_id_new(entry)); + + return git_buf_printf(out, "%s %s %s %s", old_oid, new_oid, "somesig", git_reflog_entry_message(entry)); +} + +size_t reflog_entrycount(git_repository *repo, const char *name) +{ + git_reflog *log; + size_t ret; + + cl_git_pass(git_reflog_read(&log, repo, name)); + ret = git_reflog_entrycount(log); + git_reflog_free(log); + + return ret; +} + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, const char *file, int line) +{ + git_reflog *log; + const git_reflog_entry *entry; + git_buf result = GIT_BUF_INIT; + + cl_git_pass(git_reflog_read(&log, repo, reflog)); + entry = git_reflog_entry_byindex(log, idx); + if (entry == NULL) + clar__fail(file, line, "Reflog has no such entry", NULL, 1); + + if (old_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, old_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_buf_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, old_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_buf_puts(&result, "\"\n"); + } + git__free(oid); + } + } + if (new_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, new_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_buf_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, new_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_buf_puts(&result, "\"\n"); + } + git__free(oid); + } + } + + if (email && strcmp(email, git_reflog_entry_committer(entry)->email) != 0) + git_buf_printf(&result, "\tEmail: \"%s\" != \"%s\"\n", email, git_reflog_entry_committer(entry)->email); + + if (message) { + const char *entry_msg = git_reflog_entry_message(entry); + if (entry_msg == NULL) entry_msg = ""; + + if (entry_msg && strcmp(message, entry_msg) != 0) + git_buf_printf(&result, "\tMessage: \"%s\" != \"%s\"\n", message, entry_msg); + } + if (git_buf_len(&result) != 0) + clar__fail(file, line, "Reflog entry mismatch", git_buf_cstr(&result), 1); + + git_buf_free(&result); + git_reflog_free(log); +} + +void reflog_print(git_repository *repo, const char *reflog_name) +{ + git_reflog *reflog; + size_t idx; + git_buf out = GIT_BUF_INIT; + + git_reflog_read(&reflog, repo, reflog_name); + + for (idx = 0; idx < git_reflog_entrycount(reflog); idx++) { + const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, idx); + reflog_entry_tostr(&out, entry); + git_buf_putc(&out, '\n'); + } + + fprintf(stderr, "%s", git_buf_cstr(&out)); + git_buf_free(&out); + git_reflog_free(reflog); +} diff --git a/tests/refs/reflog/reflog_helpers.h b/tests/refs/reflog/reflog_helpers.h new file mode 100644 index 000000000..80814ea28 --- /dev/null +++ b/tests/refs/reflog/reflog_helpers.h @@ -0,0 +1,10 @@ +size_t reflog_entrycount(git_repository *repo, const char *name); + +#define cl_reflog_check_entry(repo, reflog, idx, old_spec, new_spec, email, message) \ + cl_reflog_check_entry_(repo, reflog, idx, old_spec, new_spec, email, message, __FILE__, __LINE__) + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, const char *file, int line); + +void reflog_print(git_repository *repo, const char *reflog_name); diff --git a/tests/refs/rename.c b/tests/refs/rename.c index 6106e6c67..3e0be313c 100644 --- a/tests/refs/rename.c +++ b/tests/refs/rename.c @@ -366,22 +366,3 @@ void test_refs_rename__propagate_eexists(void) git_reference_free(ref); } - -void test_refs_rename__writes_to_reflog(void) -{ - git_reference *ref, *new_ref; - git_reflog *log; - const git_reflog_entry *entry; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_git_pass(git_reference_rename(&new_ref, ref, ref_one_name_new, false, - "message")); - cl_git_pass(git_reflog_read(&log, g_repo, git_reference_name(new_ref))); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s("message", git_reflog_entry_message(entry)); - cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); - - git_reflog_free(log); - git_reference_free(ref); - git_reference_free(new_ref); -} diff --git a/tests/refs/settargetwithlog.c b/tests/refs/settargetwithlog.c deleted file mode 100644 index 58fbb5fee..000000000 --- a/tests/refs/settargetwithlog.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *br2_tip = "a4a7dce85cf63874e984719f4fdd239f5145052f"; -static const char *master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; -static const char *br2_name = "refs/heads/br2"; - -static git_repository *g_repo; - -void test_refs_settargetwithlog__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_settargetwithlog__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_settargetwithlog__updating_a_direct_reference_adds_a_reflog_entry(void) -{ - git_reference *reference, *reference_out; - git_oid current_id, target_id; - git_reflog *reflog; - const git_reflog_entry *entry; - - const char *message = "You've been logged, mate!"; - - git_oid_fromstr(¤t_id, br2_tip); - git_oid_fromstr(&target_id, master_tip); - - cl_git_pass(git_reference_lookup(&reference, g_repo, br2_name)); - - cl_git_pass(git_reference_set_target( - &reference_out, reference, &target_id, message)); - - cl_git_pass(git_reflog_read(&reflog, g_repo, br2_name)); - - entry = git_reflog_entry_byindex(reflog, 0); - cl_assert_equal_oid(¤t_id, &entry->oid_old); - cl_assert_equal_oid(&target_id, &entry->oid_cur); - cl_assert_equal_s(message, entry->msg); - - git_reflog_free(reflog); - git_reference_free(reference_out); - git_reference_free(reference); -} diff --git a/tests/repo/head.c b/tests/repo/head.c index d02116087..ed3b3dcc3 100644 --- a/tests/repo/head.c +++ b/tests/repo/head.c @@ -18,40 +18,6 @@ void test_repo_head__cleanup(void) cl_git_sandbox_cleanup(); } -static void check_last_reflog_entry(const char *email, const char *message) -{ - git_reflog *log; - const git_reflog_entry *entry; - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - cl_assert(git_reflog_entrycount(log) > 0); - entry = git_reflog_entry_byindex(log, 0); - if (email) - cl_assert_equal_s(email, git_reflog_entry_committer(entry)->email); - if (message) - cl_assert_equal_s(message, git_reflog_entry_message(entry)); - git_reflog_free(log); -} - -void test_repo_head__head_detached(void) -{ - git_reference *ref; - - cl_assert_equal_i(false, git_repository_head_detached(repo)); - - cl_git_pass(git_repository_detach_head(repo)); - check_last_reflog_entry(g_email, "checkout: moving from master to a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_assert_equal_i(true, git_repository_head_detached(repo)); - - /* take the repo back to it's original state */ - cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master", - true, "REATTACH")); - git_reference_free(ref); - - check_last_reflog_entry(g_email, "REATTACH"); - cl_assert_equal_i(false, git_repository_head_detached(repo)); -} - void test_repo_head__unborn_head(void) { git_reference *ref; @@ -214,252 +180,3 @@ void test_repo_head__can_tell_if_an_unborn_head_is_detached(void) cl_assert_equal_i(false, git_repository_head_detached(repo)); } - -static void test_reflog(git_repository *repo, size_t idx, - const char *old_spec, const char *new_spec, - const char *email, const char *message) -{ - git_reflog *log; - const git_reflog_entry *entry; - - cl_git_pass(git_reflog_read(&log, repo, "HEAD")); - entry = git_reflog_entry_byindex(log, idx); - - if (old_spec) { - git_object *obj; - cl_git_pass(git_revparse_single(&obj, repo, old_spec)); - cl_assert_equal_oid(git_object_id(obj), git_reflog_entry_id_old(entry)); - git_object_free(obj); - } - if (new_spec) { - git_object *obj; - cl_git_pass(git_revparse_single(&obj, repo, new_spec)); - cl_assert_equal_oid(git_object_id(obj), git_reflog_entry_id_new(entry)); - git_object_free(obj); - } - - if (email) { - cl_assert_equal_s(email, git_reflog_entry_committer(entry)->email); - } - if (message) { - cl_assert_equal_s(message, git_reflog_entry_message(entry)); - } - - git_reflog_free(log); -} - -void test_repo_head__setting_head_updates_reflog(void) -{ - git_object *tag; - git_signature *sig; - git_annotated_commit *annotated; - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - cl_git_pass(git_repository_set_head(repo, "refs/heads/unborn")); - cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); - cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); - cl_git_pass(git_repository_set_head(repo, "refs/remotes/test/master")); - - test_reflog(repo, 4, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from master to haacked"); - test_reflog(repo, 3, NULL, "tags/test^{commit}", "foo@example.com", "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); - test_reflog(repo, 2, "tags/test^{commit}", "refs/heads/haacked", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); - test_reflog(repo, 1, "refs/heads/haacked", "tags/test^{commit}", "foo@example.com", "checkout: moving from haacked to test"); - test_reflog(repo, 0, "tags/test^{commit}", "refs/remotes/test/master", "foo@example.com", "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master"); - - cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "haacked~0")); - cl_git_pass(git_repository_set_head_detached_from_annotated(repo, annotated)); - - test_reflog(repo, 0, NULL, "refs/heads/haacked", "foo@example.com", "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0"); - - git_annotated_commit_free(annotated); - git_object_free(tag); - git_signature_free(sig); -} - -static void assert_head_reflog(git_repository *repo, size_t idx, - const char *old_id, const char *new_id, const char *message) -{ - git_reflog *log; - const git_reflog_entry *entry; - char id_str[GIT_OID_HEXSZ + 1] = {0}; - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - entry = git_reflog_entry_byindex(log, idx); - - git_oid_fmt(id_str, git_reflog_entry_id_old(entry)); - cl_assert_equal_s(old_id, id_str); - - git_oid_fmt(id_str, git_reflog_entry_id_new(entry)); - cl_assert_equal_s(new_id, id_str); - - cl_assert_equal_s(message, git_reflog_entry_message(entry)); - - git_reflog_free(log); -} - -void test_repo_head__detaching_writes_reflog(void) -{ - git_signature *sig; - git_oid id; - const char *msg; - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; - git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); - cl_git_pass(git_repository_set_head_detached(repo, &id)); - assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", msg); - - msg = "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"; - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - assert_head_reflog(repo, 0, "e90810b8df3e80c413d903f631643c716887138d", - "258f0e2a959a364e40ed6603d5d44fbb24765b10", msg); - - git_signature_free(sig); -} - -void test_repo_head__orphan_branch_does_not_count(void) -{ - git_oid id; - const char *msg; - - /* Have something known */ - msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; - git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); - cl_git_pass(git_repository_set_head_detached(repo, &id)); - assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", msg); - - /* Switching to an orphan branch does not write tot he reflog */ - cl_git_pass(git_repository_set_head(repo, "refs/heads/orphan")); - assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", msg); - - /* And coming back, we set the source to zero */ - msg = "checkout: moving from orphan to haacked"; - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - assert_head_reflog(repo, 0, "0000000000000000000000000000000000000000", - "258f0e2a959a364e40ed6603d5d44fbb24765b10", msg); -} - -void test_repo_head__set_to_current_target(void) -{ - git_reflog *log; - size_t nentries, nentries_after; - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - nentries = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked")); - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - nentries_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nentries + 1, nentries_after); -} - -void test_repo_head__branch_birth(void) -{ - git_signature *sig; - git_oid id; - git_tree *tree; - git_reference *ref; - const char *msg; - git_reflog *log; - size_t nentries, nentries_after; - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - nentries = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - cl_git_pass(git_repository_head(&ref, repo)); - cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJ_TREE)); - git_reference_free(ref); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/orphan")); - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - nentries_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nentries, nentries_after); - - msg = "message 2"; - cl_git_pass(git_commit_create(&id, repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); - - git_tree_free(tree); - - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/orphan")); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - git_reflog_free(log); - - cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE)); - nentries_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nentries + 1, nentries_after); - - git_signature_free(sig); - -} - -static size_t entrycount(git_repository *repo, const char *name) -{ - git_reflog *log; - size_t ret; - - cl_git_pass(git_reflog_read(&log, repo, name)); - ret = git_reflog_entrycount(log); - git_reflog_free(log); - - return ret; -} - -void test_repo_head__symref_chain(void) -{ - git_signature *sig; - git_oid id; - git_tree *tree; - git_reference *ref; - const char *msg; - size_t nentries, nentries_master; - - nentries = entrycount(repo, GIT_HEAD_FILE); - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - cl_git_pass(git_repository_head(&ref, repo)); - cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJ_TREE)); - git_reference_free(ref); - - nentries_master = entrycount(repo, "refs/heads/master"); - - msg = "message 1"; - cl_git_pass(git_reference_symbolic_create(&ref, repo, "refs/heads/master", "refs/heads/foo", 1, msg)); - git_reference_free(ref); - - cl_assert_equal_i(0, entrycount(repo, "refs/heads/foo")); - cl_assert_equal_i(nentries, entrycount(repo, GIT_HEAD_FILE)); - cl_assert_equal_i(nentries_master, entrycount(repo, "refs/heads/master")); - - msg = "message 2"; - cl_git_pass(git_commit_create(&id, repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); - git_tree_free(tree); - - cl_assert_equal_i(1, entrycount(repo, "refs/heads/foo")); - cl_assert_equal_i(nentries +1, entrycount(repo, GIT_HEAD_FILE)); - cl_assert_equal_i(nentries_master, entrycount(repo, "refs/heads/master")); - - git_signature_free(sig); - -} diff --git a/tests/repo/init.c b/tests/repo/init.c index 04d4a5c5e..0f969eb18 100644 --- a/tests/repo/init.c +++ b/tests/repo/init.c @@ -320,6 +320,17 @@ void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) assert_config_entry_on_init_bytype("core.logallrefupdates", true, false); } +void test_repo_init__empty_template_path(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + opts.template_path = ""; + + cl_git_pass(git_futils_mkdir("foo", 0755, 0)); + cl_git_pass(git_repository_init_ext(&_repo, "foo", &opts)); + + cleanup_repository("foo"); +} + void test_repo_init__extended_0(void) { git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; @@ -744,7 +755,7 @@ void test_repo_init__init_with_initial_commit(void) /* Initialize the repository */ cl_git_pass(git_repository_init(&_repo, "committed", 0)); - /* Init will be automatically created when requested for a new repo */ + /* Index will be automatically created when requested for a new repo */ cl_git_pass(git_repository_index(&index, _repo)); /* Create a file so we can commit it diff --git a/tests/repo/open.c b/tests/repo/open.c index 3239b6fec..ab36dd587 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -180,6 +180,8 @@ void test_repo_open__from_git_new_workdir(void) cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); git_repository_free(repo2); +#else + cl_skip(); #endif } diff --git a/tests/resources/merge-recursive/.gitted/objects/03/9d0da126f24b819a5a38186249c7f96be3824c b/tests/resources/merge-recursive/.gitted/objects/03/9d0da126f24b819a5a38186249c7f96be3824c new file mode 100644 index 000000000..79ad635ff Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/03/9d0da126f24b819a5a38186249c7f96be3824c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/05/63b7706dcdcf94bc0c02cd96c137940278eca9 b/tests/resources/merge-recursive/.gitted/objects/05/63b7706dcdcf94bc0c02cd96c137940278eca9 new file mode 100644 index 000000000..c6d38a7d3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/05/63b7706dcdcf94bc0c02cd96c137940278eca9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/08/f01e1bff7e442d574eb221913515b4bd27ccd6 b/tests/resources/merge-recursive/.gitted/objects/08/f01e1bff7e442d574eb221913515b4bd27ccd6 new file mode 100644 index 000000000..c2fbf5ba7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/08/f01e1bff7e442d574eb221913515b4bd27ccd6 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0b/beee1982b493330e375a85bbfddaba3d561556 b/tests/resources/merge-recursive/.gitted/objects/0b/beee1982b493330e375a85bbfddaba3d561556 new file mode 100644 index 000000000..66b927246 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0b/beee1982b493330e375a85bbfddaba3d561556 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0c/e202f64fa8356c1a32835fce4058ca76b0c7ed b/tests/resources/merge-recursive/.gitted/objects/0c/e202f64fa8356c1a32835fce4058ca76b0c7ed new file mode 100644 index 000000000..9a84b7149 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0c/e202f64fa8356c1a32835fce4058ca76b0c7ed differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0e/c39d71c1b074905350ce20ce3f0629f737a2a9 b/tests/resources/merge-recursive/.gitted/objects/0e/c39d71c1b074905350ce20ce3f0629f737a2a9 new file mode 100644 index 000000000..68808d2f3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0e/c39d71c1b074905350ce20ce3f0629f737a2a9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/17/946ad3088f931102e5d81f94cf2825fc188953 b/tests/resources/merge-recursive/.gitted/objects/17/946ad3088f931102e5d81f94cf2825fc188953 new file mode 100644 index 000000000..9cc133e94 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/17/946ad3088f931102e5d81f94cf2825fc188953 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/18/2d0d250d1d7adcc60c178be5be98358b3a2fd1 b/tests/resources/merge-recursive/.gitted/objects/18/2d0d250d1d7adcc60c178be5be98358b3a2fd1 new file mode 100644 index 000000000..96674c89b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/18/2d0d250d1d7adcc60c178be5be98358b3a2fd1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/1b/c7bcccf4bbdc8bfba2331a37ad5e9cf1dd321c b/tests/resources/merge-recursive/.gitted/objects/1b/c7bcccf4bbdc8bfba2331a37ad5e9cf1dd321c new file mode 100644 index 000000000..811716700 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/1b/c7bcccf4bbdc8bfba2331a37ad5e9cf1dd321c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/1b/de1883de4977ea3e664b315da951d1f614c3b1 b/tests/resources/merge-recursive/.gitted/objects/1b/de1883de4977ea3e664b315da951d1f614c3b1 new file mode 100644 index 000000000..67daf36ae Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/1b/de1883de4977ea3e664b315da951d1f614c3b1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/23/b427bf6278724433e64ef4cf6dc166c4f2e246 b/tests/resources/merge-recursive/.gitted/objects/23/b427bf6278724433e64ef4cf6dc166c4f2e246 new file mode 100644 index 000000000..03be909e4 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/23/b427bf6278724433e64ef4cf6dc166c4f2e246 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/23/cf2687a9327d55abbbd788ff04fa932072aebc b/tests/resources/merge-recursive/.gitted/objects/23/cf2687a9327d55abbbd788ff04fa932072aebc new file mode 100644 index 000000000..5cc665a0c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/23/cf2687a9327d55abbbd788ff04fa932072aebc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/26/d3c94459b4faa08f009b15867993ca34153592 b/tests/resources/merge-recursive/.gitted/objects/26/d3c94459b4faa08f009b15867993ca34153592 new file mode 100644 index 000000000..dd7b1a245 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/26/d3c94459b4faa08f009b15867993ca34153592 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/2c/ba583804a4a6fad1baf97c959be447238d1489 b/tests/resources/merge-recursive/.gitted/objects/2c/ba583804a4a6fad1baf97c959be447238d1489 new file mode 100644 index 000000000..c0a60a172 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/2c/ba583804a4a6fad1baf97c959be447238d1489 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/2e/7ae0d42fb7b6126f6a08ac6314ac07833a52f6 b/tests/resources/merge-recursive/.gitted/objects/2e/7ae0d42fb7b6126f6a08ac6314ac07833a52f6 new file mode 100644 index 000000000..6a9651c76 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/2e/7ae0d42fb7b6126f6a08ac6314ac07833a52f6 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/30/39c07db695c8c99d0a7c7e32f0afe40eae0be0 b/tests/resources/merge-recursive/.gitted/objects/30/39c07db695c8c99d0a7c7e32f0afe40eae0be0 new file mode 100644 index 000000000..cc21b5c31 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/30/39c07db695c8c99d0a7c7e32f0afe40eae0be0 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/35/8efd6f589384fa8baf92234db9c7899a53916e b/tests/resources/merge-recursive/.gitted/objects/35/8efd6f589384fa8baf92234db9c7899a53916e new file mode 100644 index 000000000..aefc81ad5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/35/8efd6f589384fa8baf92234db9c7899a53916e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/35/dda4f3f9b3794d92a46d908790e550ed100eae b/tests/resources/merge-recursive/.gitted/objects/35/dda4f3f9b3794d92a46d908790e550ed100eae new file mode 100644 index 000000000..d6cabb419 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/35/dda4f3f9b3794d92a46d908790e550ed100eae differ diff --git a/tests/resources/merge-recursive/.gitted/objects/36/71e42c8c8302d1a71c0ed7bf2b0a938e9e20f9 b/tests/resources/merge-recursive/.gitted/objects/36/71e42c8c8302d1a71c0ed7bf2b0a938e9e20f9 new file mode 100644 index 000000000..4f70ad6d4 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/36/71e42c8c8302d1a71c0ed7bf2b0a938e9e20f9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/39/78944e4cd53edcc10a170ab2ff142f7295b958 b/tests/resources/merge-recursive/.gitted/objects/39/78944e4cd53edcc10a170ab2ff142f7295b958 new file mode 100644 index 000000000..b18fd4836 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/39/78944e4cd53edcc10a170ab2ff142f7295b958 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3a/0dc89a8bd20e74fae69d2e038b47360fafb02e b/tests/resources/merge-recursive/.gitted/objects/3a/0dc89a8bd20e74fae69d2e038b47360fafb02e new file mode 100644 index 000000000..fdff502d1 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3a/0dc89a8bd20e74fae69d2e038b47360fafb02e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3a/8c70144d0334721154b1e0529716b368483d6f b/tests/resources/merge-recursive/.gitted/objects/3a/8c70144d0334721154b1e0529716b368483d6f new file mode 100644 index 000000000..958d17d35 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3a/8c70144d0334721154b1e0529716b368483d6f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3e/eff81b57a0ac15a5ab6bb3a8e92511a01a429c b/tests/resources/merge-recursive/.gitted/objects/3e/eff81b57a0ac15a5ab6bb3a8e92511a01a429c new file mode 100644 index 000000000..6a6c65460 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3e/eff81b57a0ac15a5ab6bb3a8e92511a01a429c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/40/9f5d072decec684331672f2d6c0a9bc3640adb b/tests/resources/merge-recursive/.gitted/objects/40/9f5d072decec684331672f2d6c0a9bc3640adb new file mode 100644 index 000000000..b4c9005e1 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/40/9f5d072decec684331672f2d6c0a9bc3640adb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/44/faf5fba1af850dae54f8b2345938d3c7ae479f b/tests/resources/merge-recursive/.gitted/objects/44/faf5fba1af850dae54f8b2345938d3c7ae479f new file mode 100644 index 000000000..d0bc09902 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/44/faf5fba1af850dae54f8b2345938d3c7ae479f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4a/06b258fed8a4d15967ec4253ae7366b70f727d b/tests/resources/merge-recursive/.gitted/objects/4a/06b258fed8a4d15967ec4253ae7366b70f727d new file mode 100644 index 000000000..d3e181501 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4a/06b258fed8a4d15967ec4253ae7366b70f727d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/resources/merge-recursive/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4c/62e9482ed42c1a6d08891906b26126daa4a8f5 b/tests/resources/merge-recursive/.gitted/objects/4c/62e9482ed42c1a6d08891906b26126daa4a8f5 new file mode 100644 index 000000000..f3cee9a2d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4c/62e9482ed42c1a6d08891906b26126daa4a8f5 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/50/4dd93fb5b9c2a28c094c6e84ef0606de1e9b5c b/tests/resources/merge-recursive/.gitted/objects/50/4dd93fb5b9c2a28c094c6e84ef0606de1e9b5c new file mode 100644 index 000000000..214d3076d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/50/4dd93fb5b9c2a28c094c6e84ef0606de1e9b5c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/50/dfa64a56b488fe8082371b182c8a3e3c942332 b/tests/resources/merge-recursive/.gitted/objects/50/dfa64a56b488fe8082371b182c8a3e3c942332 new file mode 100644 index 000000000..7c90c99af Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/50/dfa64a56b488fe8082371b182c8a3e3c942332 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/51/135c5884d7dd132fef3b432cca5826bab98f37 b/tests/resources/merge-recursive/.gitted/objects/51/135c5884d7dd132fef3b432cca5826bab98f37 new file mode 100644 index 000000000..95818f144 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/51/135c5884d7dd132fef3b432cca5826bab98f37 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/51/60ab78c1973dcd7cdebe2345dc8fcfc755e76d b/tests/resources/merge-recursive/.gitted/objects/51/60ab78c1973dcd7cdebe2345dc8fcfc755e76d new file mode 100644 index 000000000..f3f99d7fe Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/51/60ab78c1973dcd7cdebe2345dc8fcfc755e76d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/56/fcbad344aafe519bafcc33c87b8e64849d82ab b/tests/resources/merge-recursive/.gitted/objects/56/fcbad344aafe519bafcc33c87b8e64849d82ab new file mode 100644 index 000000000..06bea32e6 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/56/fcbad344aafe519bafcc33c87b8e64849d82ab differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5a/47615db824433f816ba62217dda6d46c5a7640 b/tests/resources/merge-recursive/.gitted/objects/5a/47615db824433f816ba62217dda6d46c5a7640 new file mode 100644 index 000000000..c1e30ce54 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5a/47615db824433f816ba62217dda6d46c5a7640 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5c/27b5f7c6f6dd4e5b4d64976741d56c2df8f48a b/tests/resources/merge-recursive/.gitted/objects/5c/27b5f7c6f6dd4e5b4d64976741d56c2df8f48a new file mode 100644 index 000000000..783d085a7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5c/27b5f7c6f6dd4e5b4d64976741d56c2df8f48a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5d/998d5f278aff5693711bc48f6852aac4b603ad b/tests/resources/merge-recursive/.gitted/objects/5d/998d5f278aff5693711bc48f6852aac4b603ad new file mode 100644 index 000000000..a7795f59b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5d/998d5f278aff5693711bc48f6852aac4b603ad differ diff --git a/tests/resources/merge-recursive/.gitted/objects/61/6d1209afac499b005f68309e1593b44899b054 b/tests/resources/merge-recursive/.gitted/objects/61/6d1209afac499b005f68309e1593b44899b054 new file mode 100644 index 000000000..6a06214d7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/61/6d1209afac499b005f68309e1593b44899b054 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/71/c50785d8d512293bd3af838b131f3da5829ebc b/tests/resources/merge-recursive/.gitted/objects/71/c50785d8d512293bd3af838b131f3da5829ebc new file mode 100644 index 000000000..23c40332e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/71/c50785d8d512293bd3af838b131f3da5829ebc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/75/afa96db00c26c6ebf3b377615b4e2a20563ee4 b/tests/resources/merge-recursive/.gitted/objects/75/afa96db00c26c6ebf3b377615b4e2a20563ee4 new file mode 100644 index 000000000..11d7f94a9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/75/afa96db00c26c6ebf3b377615b4e2a20563ee4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/76/6afbfd7d42f757f1fac9ea550c9fcbc8041b89 b/tests/resources/merge-recursive/.gitted/objects/76/6afbfd7d42f757f1fac9ea550c9fcbc8041b89 new file mode 100644 index 000000000..a5af38357 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/76/6afbfd7d42f757f1fac9ea550c9fcbc8041b89 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7c/61830f8b8632665bb44ae5d219f520f5aa5bb4 b/tests/resources/merge-recursive/.gitted/objects/7c/61830f8b8632665bb44ae5d219f520f5aa5bb4 new file mode 100644 index 000000000..04b10f7ee Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7c/61830f8b8632665bb44ae5d219f520f5aa5bb4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7c/9a30d8dcee320a3b1f9ed10b582479faa9d3a1 b/tests/resources/merge-recursive/.gitted/objects/7c/9a30d8dcee320a3b1f9ed10b582479faa9d3a1 new file mode 100644 index 000000000..7500a241e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7c/9a30d8dcee320a3b1f9ed10b582479faa9d3a1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7e/2d2bad4fc21f2832ca2afd48b1f95ab37ffb92 b/tests/resources/merge-recursive/.gitted/objects/7e/2d2bad4fc21f2832ca2afd48b1f95ab37ffb92 new file mode 100644 index 000000000..11f96c9c8 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7e/2d2bad4fc21f2832ca2afd48b1f95ab37ffb92 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7e/70a7872576bba7e299cde45abb7da1e4d7ba81 b/tests/resources/merge-recursive/.gitted/objects/7e/70a7872576bba7e299cde45abb7da1e4d7ba81 new file mode 100644 index 000000000..cab463975 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7e/70a7872576bba7e299cde45abb7da1e4d7ba81 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7f/9c1d78d760cbfa99273bc1ef642d994c6baa5c b/tests/resources/merge-recursive/.gitted/objects/7f/9c1d78d760cbfa99273bc1ef642d994c6baa5c new file mode 100644 index 000000000..46c8e8523 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7f/9c1d78d760cbfa99273bc1ef642d994c6baa5c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/81/60cb53660b86c954144b8dbbb0b6e4db4ba6ba b/tests/resources/merge-recursive/.gitted/objects/81/60cb53660b86c954144b8dbbb0b6e4db4ba6ba new file mode 100644 index 000000000..19d32c770 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/81/60cb53660b86c954144b8dbbb0b6e4db4ba6ba differ diff --git a/tests/resources/merge-recursive/.gitted/objects/8f/1b918542a5fe9b3bb7a8770a7525ad5b3b5864 b/tests/resources/merge-recursive/.gitted/objects/8f/1b918542a5fe9b3bb7a8770a7525ad5b3b5864 new file mode 100644 index 000000000..5ecb3e57b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/8f/1b918542a5fe9b3bb7a8770a7525ad5b3b5864 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/97/5dd228fd1b0cacf2988167088fd1190c9ac0f5 b/tests/resources/merge-recursive/.gitted/objects/97/5dd228fd1b0cacf2988167088fd1190c9ac0f5 new file mode 100644 index 000000000..96658c4fb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/97/5dd228fd1b0cacf2988167088fd1190c9ac0f5 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/98/5b725cf91c6861b5e7a419415d03cbcf5f16ca b/tests/resources/merge-recursive/.gitted/objects/98/5b725cf91c6861b5e7a419415d03cbcf5f16ca new file mode 100644 index 000000000..bc95c6fc5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/98/5b725cf91c6861b5e7a419415d03cbcf5f16ca differ diff --git a/tests/resources/merge-recursive/.gitted/objects/98/cacbdd1fac7bbab54a6c7c97aa2103219e08b8 b/tests/resources/merge-recursive/.gitted/objects/98/cacbdd1fac7bbab54a6c7c97aa2103219e08b8 new file mode 100644 index 000000000..13344d86c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/98/cacbdd1fac7bbab54a6c7c97aa2103219e08b8 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/99/754e36599906b81b917447280c4918269e14ff b/tests/resources/merge-recursive/.gitted/objects/99/754e36599906b81b917447280c4918269e14ff new file mode 100644 index 000000000..40455efab Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/99/754e36599906b81b917447280c4918269e14ff differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9a/228c1ee87f286202ec9a25de837a18550013b5 b/tests/resources/merge-recursive/.gitted/objects/9a/228c1ee87f286202ec9a25de837a18550013b5 new file mode 100644 index 000000000..d6bcd5262 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9a/228c1ee87f286202ec9a25de837a18550013b5 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9c/dde216049c6a5ccddac0ad81f604419d8990ed b/tests/resources/merge-recursive/.gitted/objects/9c/dde216049c6a5ccddac0ad81f604419d8990ed new file mode 100644 index 000000000..9f2c92851 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9c/dde216049c6a5ccddac0ad81f604419d8990ed differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a0/ce8909834f389b4f8be6a6ec420868422d40a1 b/tests/resources/merge-recursive/.gitted/objects/a0/ce8909834f389b4f8be6a6ec420868422d40a1 new file mode 100644 index 000000000..5f6643c59 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a0/ce8909834f389b4f8be6a6ec420868422d40a1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a2/817ed0e8ca6fe52bf0a20b2f50eb94b9ea5415 b/tests/resources/merge-recursive/.gitted/objects/a2/817ed0e8ca6fe52bf0a20b2f50eb94b9ea5415 new file mode 100644 index 000000000..a043a8e03 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a2/817ed0e8ca6fe52bf0a20b2f50eb94b9ea5415 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a3/5aa65d86215fce909fc0bcce8949d12becba44 b/tests/resources/merge-recursive/.gitted/objects/a3/5aa65d86215fce909fc0bcce8949d12becba44 new file mode 100644 index 000000000..e71e9ec44 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a3/5aa65d86215fce909fc0bcce8949d12becba44 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a3/ca4c462e93fee824c8ad500917ae34b800dea4 b/tests/resources/merge-recursive/.gitted/objects/a3/ca4c462e93fee824c8ad500917ae34b800dea4 new file mode 100644 index 000000000..dff9e9750 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a3/ca4c462e93fee824c8ad500917ae34b800dea4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a6/64873b1c0b9a1ed300f8644dde536fdaa3a34f b/tests/resources/merge-recursive/.gitted/objects/a6/64873b1c0b9a1ed300f8644dde536fdaa3a34f new file mode 100644 index 000000000..f6b66dada Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a6/64873b1c0b9a1ed300f8644dde536fdaa3a34f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a9/9bf55117ab1958171fccfeb19885f707bd08fd b/tests/resources/merge-recursive/.gitted/objects/a9/9bf55117ab1958171fccfeb19885f707bd08fd new file mode 100644 index 000000000..9c3a3ec28 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a9/9bf55117ab1958171fccfeb19885f707bd08fd differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b0/1de62cf11945685c98ec671edabdff3e90ddc5 b/tests/resources/merge-recursive/.gitted/objects/b0/1de62cf11945685c98ec671edabdff3e90ddc5 new file mode 100644 index 000000000..786c9a510 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b0/1de62cf11945685c98ec671edabdff3e90ddc5 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b0/4823b75c8220b89c2f8da54709cda262304cd3 b/tests/resources/merge-recursive/.gitted/objects/b0/4823b75c8220b89c2f8da54709cda262304cd3 new file mode 100644 index 000000000..81714b06d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b0/4823b75c8220b89c2f8da54709cda262304cd3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b1/71224a4f604b6091072007765419b14c232c1d b/tests/resources/merge-recursive/.gitted/objects/b1/71224a4f604b6091072007765419b14c232c1d new file mode 100644 index 000000000..987d5fec1 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b1/71224a4f604b6091072007765419b14c232c1d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b2/908343e3c16249d0036dd444fc0d4662cd8c0e b/tests/resources/merge-recursive/.gitted/objects/b2/908343e3c16249d0036dd444fc0d4662cd8c0e new file mode 100644 index 000000000..7f0e0aba6 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b2/908343e3c16249d0036dd444fc0d4662cd8c0e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b6/bd0f9952f396e757d3f91e08c59a7e91707201 b/tests/resources/merge-recursive/.gitted/objects/b6/bd0f9952f396e757d3f91e08c59a7e91707201 new file mode 100644 index 000000000..87cb8fa04 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b6/bd0f9952f396e757d3f91e08c59a7e91707201 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b7/de2b52ba055688061355fad1599a5d214ce8f8 b/tests/resources/merge-recursive/.gitted/objects/b7/de2b52ba055688061355fad1599a5d214ce8f8 new file mode 100644 index 000000000..6fbf58187 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b7/de2b52ba055688061355fad1599a5d214ce8f8 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b8/a3b657edcf31e6365a2f1c45d45e6c9ebe8f02 b/tests/resources/merge-recursive/.gitted/objects/b8/a3b657edcf31e6365a2f1c45d45e6c9ebe8f02 new file mode 100644 index 000000000..0b8404bf3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b8/a3b657edcf31e6365a2f1c45d45e6c9ebe8f02 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ba/9dcfe079848e8e5c1b53bc3b6e47ff57f6e481 b/tests/resources/merge-recursive/.gitted/objects/ba/9dcfe079848e8e5c1b53bc3b6e47ff57f6e481 new file mode 100644 index 000000000..c43b79da3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ba/9dcfe079848e8e5c1b53bc3b6e47ff57f6e481 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/bb/4e0014fb09d24312f0af37c7a45e5488f19510 b/tests/resources/merge-recursive/.gitted/objects/bb/4e0014fb09d24312f0af37c7a45e5488f19510 new file mode 100644 index 000000000..ca45760cd Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/bb/4e0014fb09d24312f0af37c7a45e5488f19510 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c0/dcb4bfcd86e65a822090aa7a0455413828886b b/tests/resources/merge-recursive/.gitted/objects/c0/dcb4bfcd86e65a822090aa7a0455413828886b new file mode 100644 index 000000000..f8fe20180 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c0/dcb4bfcd86e65a822090aa7a0455413828886b differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c4/44758b02d4af6e3145ac2fc0e3ed02199cf7ec b/tests/resources/merge-recursive/.gitted/objects/c4/44758b02d4af6e3145ac2fc0e3ed02199cf7ec new file mode 100644 index 000000000..b2f6662a0 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c4/44758b02d4af6e3145ac2fc0e3ed02199cf7ec differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c7/f3257db72e885d6612080c003e0f2ef480e0c4 b/tests/resources/merge-recursive/.gitted/objects/c7/f3257db72e885d6612080c003e0f2ef480e0c4 new file mode 100644 index 000000000..255624e93 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c7/f3257db72e885d6612080c003e0f2ef480e0c4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ce/0d744cd2e18eacf883d43471636f231c0995e3 b/tests/resources/merge-recursive/.gitted/objects/ce/0d744cd2e18eacf883d43471636f231c0995e3 new file mode 100644 index 000000000..63c457a2c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ce/0d744cd2e18eacf883d43471636f231c0995e3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d0/97bcf99adb1022a6b7d2e94fed2031ebd9d89c b/tests/resources/merge-recursive/.gitted/objects/d0/97bcf99adb1022a6b7d2e94fed2031ebd9d89c new file mode 100644 index 000000000..51ffec090 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d0/97bcf99adb1022a6b7d2e94fed2031ebd9d89c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d0/c9bd6e2a3e327d81a32de51201d3bd58909f7c b/tests/resources/merge-recursive/.gitted/objects/d0/c9bd6e2a3e327d81a32de51201d3bd58909f7c new file mode 100644 index 000000000..5a0508c5d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d0/c9bd6e2a3e327d81a32de51201d3bd58909f7c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d3/482dbdca5bb83aaf3e3768359855d55aef84d7 b/tests/resources/merge-recursive/.gitted/objects/d3/482dbdca5bb83aaf3e3768359855d55aef84d7 new file mode 100644 index 000000000..8442948fa Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d3/482dbdca5bb83aaf3e3768359855d55aef84d7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d5/015f9436b2d8c842bf6616e7cf5bc54eb79ced b/tests/resources/merge-recursive/.gitted/objects/d5/015f9436b2d8c842bf6616e7cf5bc54eb79ced new file mode 100644 index 000000000..6d4446ec4 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d5/015f9436b2d8c842bf6616e7cf5bc54eb79ced differ diff --git a/tests/resources/merge-recursive/.gitted/objects/db/51adf2b699eed93e883d6425f5e6c50165a9c2 b/tests/resources/merge-recursive/.gitted/objects/db/51adf2b699eed93e883d6425f5e6c50165a9c2 new file mode 100644 index 000000000..41b481efb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/db/51adf2b699eed93e883d6425f5e6c50165a9c2 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/db/7e2af8ca83b8943adce7ba37d85f8fe7d7d2a9 b/tests/resources/merge-recursive/.gitted/objects/db/7e2af8ca83b8943adce7ba37d85f8fe7d7d2a9 new file mode 100644 index 000000000..0a0ad6527 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/db/7e2af8ca83b8943adce7ba37d85f8fe7d7d2a9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/de/de92a05a0841faa8e4ad6700285cd208184458 b/tests/resources/merge-recursive/.gitted/objects/de/de92a05a0841faa8e4ad6700285cd208184458 new file mode 100644 index 000000000..c275071e2 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/de/de92a05a0841faa8e4ad6700285cd208184458 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e0/15ebd79a72a88b9291df11771caf56f463e8f9 b/tests/resources/merge-recursive/.gitted/objects/e0/15ebd79a72a88b9291df11771caf56f463e8f9 new file mode 100644 index 000000000..a75a6b60e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e0/15ebd79a72a88b9291df11771caf56f463e8f9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e5/20e6aaf8d1e68a433e29d4360c1e74aa4b24d1 b/tests/resources/merge-recursive/.gitted/objects/e5/20e6aaf8d1e68a433e29d4360c1e74aa4b24d1 new file mode 100644 index 000000000..bcf2dcfc5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e5/20e6aaf8d1e68a433e29d4360c1e74aa4b24d1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e6/269ce9017816d67c7189a58b6d0d22bf4b8a1a b/tests/resources/merge-recursive/.gitted/objects/e6/269ce9017816d67c7189a58b6d0d22bf4b8a1a new file mode 100644 index 000000000..f9a0a27cc Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e6/269ce9017816d67c7189a58b6d0d22bf4b8a1a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e9/30c8c67848df4aa66319c5752fab6b8fdec765 b/tests/resources/merge-recursive/.gitted/objects/e9/30c8c67848df4aa66319c5752fab6b8fdec765 new file mode 100644 index 000000000..c0ba76deb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e9/30c8c67848df4aa66319c5752fab6b8fdec765 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ea/3521485adfa0b0373deaaa06db9218a22edae8 b/tests/resources/merge-recursive/.gitted/objects/ea/3521485adfa0b0373deaaa06db9218a22edae8 new file mode 100644 index 000000000..40d89843c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ea/3521485adfa0b0373deaaa06db9218a22edae8 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f0/856993e005c0d8ed2dc7cdc222cc1d89fb3c77 b/tests/resources/merge-recursive/.gitted/objects/f0/856993e005c0d8ed2dc7cdc222cc1d89fb3c77 new file mode 100644 index 000000000..22b1ad94c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f0/856993e005c0d8ed2dc7cdc222cc1d89fb3c77 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f2/9ccca75754d8476e5dad8cf250e03d43fe9e6c b/tests/resources/merge-recursive/.gitted/objects/f2/9ccca75754d8476e5dad8cf250e03d43fe9e6c new file mode 100644 index 000000000..b91872905 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f2/9ccca75754d8476e5dad8cf250e03d43fe9e6c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f3/2c284f537ff1a55d3cbfe9a37d431b6edfadc2 b/tests/resources/merge-recursive/.gitted/objects/f3/2c284f537ff1a55d3cbfe9a37d431b6edfadc2 new file mode 100644 index 000000000..be3ceccdc Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f3/2c284f537ff1a55d3cbfe9a37d431b6edfadc2 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f4/c149e7d0983e90e9ee802ff57ae3c905ba63da b/tests/resources/merge-recursive/.gitted/objects/f4/c149e7d0983e90e9ee802ff57ae3c905ba63da new file mode 100644 index 000000000..e9c675b6d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f4/c149e7d0983e90e9ee802ff57ae3c905ba63da differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f6/5de1834f57708e76d8dc25502b7f1ecbcce162 b/tests/resources/merge-recursive/.gitted/objects/f6/5de1834f57708e76d8dc25502b7f1ecbcce162 new file mode 100644 index 000000000..3d238fd8c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f6/5de1834f57708e76d8dc25502b7f1ecbcce162 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f9/c04e4e9d4aaf1e6fe7478a7cc0756554974c2b b/tests/resources/merge-recursive/.gitted/objects/f9/c04e4e9d4aaf1e6fe7478a7cc0756554974c2b new file mode 100644 index 000000000..bc44fa73a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f9/c04e4e9d4aaf1e6fe7478a7cc0756554974c2b differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-1 new file mode 100644 index 000000000..64612d486 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-2 new file mode 100644 index 000000000..bea674803 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchJ-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchK-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchK-1 new file mode 100644 index 000000000..309b38880 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchK-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchK-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchK-2 new file mode 100644 index 000000000..f958f131c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchK-2 differ diff --git a/tests/resources/merge-resolve/.gitted/objects/50/c5dc8cdfe40c688eb0a0e23be54dd57cae2e78 b/tests/resources/merge-resolve/.gitted/objects/50/c5dc8cdfe40c688eb0a0e23be54dd57cae2e78 new file mode 100644 index 000000000..c04baa14b Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/objects/50/c5dc8cdfe40c688eb0a0e23be54dd57cae2e78 differ diff --git a/tests/resources/merge-resolve/.gitted/objects/7a/a825857f87aea74ddf13d954568aa30dfcdeb4 b/tests/resources/merge-resolve/.gitted/objects/7a/a825857f87aea74ddf13d954568aa30dfcdeb4 new file mode 100644 index 000000000..b9c06303b Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/objects/7a/a825857f87aea74ddf13d954568aa30dfcdeb4 differ diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/delete-submodule b/tests/resources/merge-resolve/.gitted/refs/heads/delete-submodule new file mode 100644 index 000000000..1951316d5 Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/refs/heads/delete-submodule differ diff --git a/tests/resources/rebase-submodule/.gitmodules b/tests/resources/rebase-submodule/gitmodules similarity index 100% rename from tests/resources/rebase-submodule/.gitmodules rename to tests/resources/rebase-submodule/gitmodules diff --git a/tests/resources/rebase-submodule/my-submodule b/tests/resources/rebase-submodule/my-submodule deleted file mode 160000 index efad0b11c..000000000 --- a/tests/resources/rebase-submodule/my-submodule +++ /dev/null @@ -1 +0,0 @@ -Subproject commit efad0b11c47cb2f0220cbd6f5b0f93bb99064b00 diff --git a/tests/resources/renames/.gitted/objects/2c/136d294960f7d939f1ed1903f1ced78b874c87 b/tests/resources/renames/.gitted/objects/2c/136d294960f7d939f1ed1903f1ced78b874c87 new file mode 100644 index 000000000..51b7cea41 Binary files /dev/null and b/tests/resources/renames/.gitted/objects/2c/136d294960f7d939f1ed1903f1ced78b874c87 differ diff --git a/tests/resources/renames/.gitted/objects/84/d8efa38af7ace2b302de0adbda16b1f1cd2e1b b/tests/resources/renames/.gitted/objects/84/d8efa38af7ace2b302de0adbda16b1f1cd2e1b new file mode 100644 index 000000000..56f98fe3b Binary files /dev/null and b/tests/resources/renames/.gitted/objects/84/d8efa38af7ace2b302de0adbda16b1f1cd2e1b differ diff --git a/tests/resources/renames/.gitted/objects/89/7dda8ecb7fa2e092bc3f9e2a179d7c1b0364db b/tests/resources/renames/.gitted/objects/89/7dda8ecb7fa2e092bc3f9e2a179d7c1b0364db new file mode 100644 index 000000000..d104e66de Binary files /dev/null and b/tests/resources/renames/.gitted/objects/89/7dda8ecb7fa2e092bc3f9e2a179d7c1b0364db differ diff --git a/tests/resources/renames/.gitted/objects/95/ceb126bf79e76020d8879a8b0d50a73307a97f b/tests/resources/renames/.gitted/objects/95/ceb126bf79e76020d8879a8b0d50a73307a97f new file mode 100644 index 000000000..0486ba5b0 Binary files /dev/null and b/tests/resources/renames/.gitted/objects/95/ceb126bf79e76020d8879a8b0d50a73307a97f differ diff --git a/tests/resources/renames/.gitted/objects/be/053a189b0bbde545e0a3f59ce00b46ad29ce0d b/tests/resources/renames/.gitted/objects/be/053a189b0bbde545e0a3f59ce00b46ad29ce0d new file mode 100644 index 000000000..de7aceb62 Binary files /dev/null and b/tests/resources/renames/.gitted/objects/be/053a189b0bbde545e0a3f59ce00b46ad29ce0d differ diff --git a/tests/resources/renames/.gitted/refs/heads/delete-and-rename b/tests/resources/renames/.gitted/refs/heads/delete-and-rename new file mode 100644 index 000000000..f27fc2184 Binary files /dev/null and b/tests/resources/renames/.gitted/refs/heads/delete-and-rename differ diff --git a/tests/resources/renames/.gitted/refs/heads/rewrite-and-delete b/tests/resources/renames/.gitted/refs/heads/rewrite-and-delete new file mode 100644 index 000000000..0c0ecad78 Binary files /dev/null and b/tests/resources/renames/.gitted/refs/heads/rewrite-and-delete differ diff --git a/tests/resources/submodules.git/HEAD b/tests/resources/submodules.git/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submodules.git/HEAD differ diff --git a/tests/resources/submodules.git/config b/tests/resources/submodules.git/config new file mode 100644 index 000000000..07d359d07 Binary files /dev/null and b/tests/resources/submodules.git/config differ diff --git a/tests/resources/submodules.git/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e b/tests/resources/submodules.git/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e new file mode 100644 index 000000000..2c3c2cb61 Binary files /dev/null and b/tests/resources/submodules.git/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e differ diff --git a/tests/resources/submodules.git/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 b/tests/resources/submodules.git/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 new file mode 100644 index 000000000..c85fb5512 Binary files /dev/null and b/tests/resources/submodules.git/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 differ diff --git a/tests/resources/submodules.git/objects/97/896810b3210244a62a82458b8e0819ecfc6850 b/tests/resources/submodules.git/objects/97/896810b3210244a62a82458b8e0819ecfc6850 new file mode 100644 index 000000000..1c8dbdf9f Binary files /dev/null and b/tests/resources/submodules.git/objects/97/896810b3210244a62a82458b8e0819ecfc6850 differ diff --git a/tests/resources/submodules.git/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 b/tests/resources/submodules.git/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 new file mode 100644 index 000000000..3d78bd6be Binary files /dev/null and b/tests/resources/submodules.git/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 differ diff --git a/tests/resources/submodules.git/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 b/tests/resources/submodules.git/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 new file mode 100644 index 000000000..6e0b49e86 Binary files /dev/null and b/tests/resources/submodules.git/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 differ diff --git a/tests/resources/submodules.git/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae b/tests/resources/submodules.git/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae new file mode 100644 index 000000000..082a58941 Binary files /dev/null and b/tests/resources/submodules.git/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae differ diff --git a/tests/resources/submodules.git/objects/info/packs b/tests/resources/submodules.git/objects/info/packs new file mode 100644 index 000000000..0785ef698 Binary files /dev/null and b/tests/resources/submodules.git/objects/info/packs differ diff --git a/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx b/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx new file mode 100644 index 000000000..810fc3181 Binary files /dev/null and b/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx differ diff --git a/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack b/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack new file mode 100644 index 000000000..c25c4a73f Binary files /dev/null and b/tests/resources/submodules.git/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack differ diff --git a/tests/resources/submodules.git/refs/heads/master b/tests/resources/submodules.git/refs/heads/master new file mode 100644 index 000000000..32b935853 Binary files /dev/null and b/tests/resources/submodules.git/refs/heads/master differ diff --git a/tests/resources/testrepo/.gitted/objects/57/43a3ef145d3638a0fa28233ca92897117ad74f b/tests/resources/testrepo/.gitted/objects/57/43a3ef145d3638a0fa28233ca92897117ad74f new file mode 100644 index 000000000..85abb27cc Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/57/43a3ef145d3638a0fa28233ca92897117ad74f differ diff --git a/tests/resources/testrepo/.gitted/objects/f9/ed4af42472941da45a3ce44458455ed227a6be b/tests/resources/testrepo/.gitted/objects/f9/ed4af42472941da45a3ce44458455ed227a6be new file mode 100644 index 000000000..69c52fc9f Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/f9/ed4af42472941da45a3ce44458455ed227a6be differ diff --git a/tests/resources/testrepo/.gitted/refs/heads/executable b/tests/resources/testrepo/.gitted/refs/heads/executable new file mode 100644 index 000000000..2bdccea29 Binary files /dev/null and b/tests/resources/testrepo/.gitted/refs/heads/executable differ diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 547050c68..6a23701f3 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -177,7 +177,7 @@ void test_revwalk_basic__glob_heads_with_invalid(void) /* walking */; /* git log --branches --oneline | wc -l => 16 */ - cl_assert_equal_i(19, i); + cl_assert_equal_i(20, i); } void test_revwalk_basic__push_head(void) diff --git a/tests/status/ignore.c b/tests/status/ignore.c index 23384fb1d..dc58e8b45 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -1155,3 +1155,30 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void) refute_is_ignored("project/src/foo.c"); refute_is_ignored("project/src/foo/foo.c"); } + +void test_status_ignore__deeper(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/.gitignore", + "*.data\n" + "!dont_ignore/*.data\n"); + + cl_git_pass(p_mkdir("empty_standard_repo/dont_ignore", 0777)); + cl_git_mkfile("empty_standard_repo/foo.data", ""); + cl_git_mkfile("empty_standard_repo/bar.data", ""); + cl_git_mkfile("empty_standard_repo/dont_ignore/foo.data", ""); + cl_git_mkfile("empty_standard_repo/dont_ignore/bar.data", ""); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "foo.data")); + cl_assert_equal_i(1, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "bar.data")); + cl_assert_equal_i(1, ignored); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/foo.data")); + cl_assert_equal_i(0, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/bar.data")); + cl_assert_equal_i(0, ignored); +} diff --git a/tests/status/renames.c b/tests/status/renames.c index f482d693a..ae32d2ee0 100644 --- a/tests/status/renames.c +++ b/tests/status/renames.c @@ -590,6 +590,12 @@ static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; #endif +/* + * Create a file in NFD (canonically decomposed) format. Ensure + * that when core.precomposeunicode is false that we return paths + * in NFD, but when core.precomposeunicode is true, then we + * return paths precomposed (in NFC). + */ void test_status_renames__precomposed_unicode_rename(void) { #ifdef GIT_USE_ICONV @@ -610,7 +616,7 @@ void test_status_renames__precomposed_unicode_rename(void) { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfc }, }; - rename_file(g_repo, "sixserving.txt", nfc); + rename_file(g_repo, "sixserving.txt", nfd); opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 1345dbfd2..c6b18c166 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -1072,6 +1072,8 @@ void test_status_worktree__unreadable(void) cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); #endif } @@ -1106,6 +1108,8 @@ void test_status_worktree__unreadable_not_included(void) cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); #endif } @@ -1280,3 +1284,34 @@ void test_status_worktree__with_directory_in_pathlist(void) git_status_list_free(statuslist); } +void test_status_worktree__at_head_parent(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *statuslist; + git_tree *parent_tree; + const git_status_entry *status; + + cl_git_mkfile("empty_standard_repo/file1", "ping"); + stage_and_commit(repo, "file1"); + + cl_git_pass(git_repository_head_tree(&parent_tree, repo)); + + cl_git_mkfile("empty_standard_repo/file2", "pong"); + stage_and_commit(repo, "file2"); + + cl_git_rewritefile("empty_standard_repo/file2", "pyng"); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.baseline = parent_tree; + cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); + + cl_assert_equal_sz(1, git_status_list_entrycount(statuslist)); + status = git_status_byindex(statuslist, 0); + cl_assert(status != NULL); + cl_assert_equal_s("file2", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, status->status); + + git_tree_free(parent_tree); + git_status_list_free(statuslist); +} diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index e36fc44e0..f84f07c60 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -11,6 +11,11 @@ void test_submodule_lookup__initialize(void) g_repo = setup_fixture_submod2(); } +void test_submodule_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + void test_submodule_lookup__simple_lookup(void) { assert_submodule_exists(g_repo, "sm_unchanged"); @@ -389,7 +394,8 @@ void test_submodule_lookup__renamed(void) cl_assert_equal_i(8, data.count); } -void test_submodule_lookup_cached(void) { +void test_submodule_lookup__cached(void) +{ git_submodule *sm; git_submodule *sm2; /* See that the simple tests still pass. */ @@ -413,3 +419,29 @@ void test_submodule_lookup_cached(void) { git_submodule_free(sm); git_submodule_free(sm2); } + +void test_submodule_lookup__lookup_in_bare_repository_fails(void) +{ + git_submodule *sm; + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("submodules.git"); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "nonexisting")); +} + +static int foreach_cb(git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(sm); + GIT_UNUSED(name); + GIT_UNUSED(payload); + return 0; +} + +void test_submodule_lookup__foreach_in_bare_repository_fails(void) +{ + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("submodules.git"); + + cl_git_fail(git_submodule_foreach(g_repo, foreach_cb, NULL)); +} diff --git a/tests/transport/register.c b/tests/transport/register.c index 97aae6b20..88ba247de 100644 --- a/tests/transport/register.c +++ b/tests/transport/register.c @@ -45,6 +45,8 @@ void test_transport_register__custom_transport_ssh(void) "ssh+git://somehost:somepath", "git+ssh://somehost:somepath", "git@somehost:somepath", + "ssh://somehost:somepath%20with%20%spaces", + "ssh://somehost:somepath with spaces" }; git_transport *transport; unsigned i;