diff --git a/.mailmap b/.mailmap index c656f64c7..8479cf6c4 100644 --- a/.mailmap +++ b/.mailmap @@ -16,6 +16,7 @@ Xavier L. Sascha Cunz Authmillenon Authmillenon -Edward Thomson +Edward Thomson +Edward Thomson J. David Ibáñez Russell Belfer diff --git a/.travis.yml b/.travis.yml index 9022fdec2..2f3ffe355 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,13 +46,13 @@ matrix: - compiler: gcc env: - VALGRIND=1 - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Debug" + OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" os: linux allow_failures: - env: COVERITY=1 - env: - VALGRIND=1 - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Debug" + OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index b824a66da..43476b99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.23 + 1 +v0.24 + 1 ------- ### Changes or improvements @@ -7,6 +7,116 @@ v0.23 + 1 ### API removals +### Breaking API changes + +v0.24 +------- + +### Changes or improvements + +* Custom filters can now be registered with wildcard attributes, for + example `filter=*`. Consumers should examine the attributes parameter + of the `check` function for details. + +* Symlinks are now followed when locking a file, which can be + necessary when multiple worktrees share a base repository. + +* You can now set your own user-agent to be sent for HTTP requests by + using the `GIT_OPT_SET_USER_AGENT` with `git_libgit2_opts()`. + +* You can set custom HTTP header fields to be sent along with requests + by passing them in the fetch and push options. + +* Tree objects are now assumed to be sorted. If a tree is not + correctly formed, it will give bad results. This is the git approach + and cuts a significant amount of time when reading the trees. + +* Filter registration is now protected against concurrent + registration. + +* Filenames which are not valid on Windows in an index no longer cause + to fail to parse it on that OS. + +* Rebases can now be performed purely in-memory, without touching the + repository's workdir. + +* When adding objects to the index, or when creating new tree or commit + objects, the inputs are validated to ensure that the dependent objects + exist and are of the correct type. This object validation can be + disabled with the GIT_OPT_ENABLE_STRICT_OBJECT_CREATION option. + +* The WinHTTP transport's handling of bad credentials now behaves like + the others, asking for credentials again. + +### API additions + +* `git_config_lock()` has been added, which allow for + transactional/atomic complex updates to the configuration, removing + the opportunity for concurrent operations and not committing any + changes until the unlock. + +* `git_diff_options` added a new callback `progress_cb` to report on the + progress of the diff as files are being compared. The documentation of + the existing callback `notify_cb` was updated to reflect that it only + gets called when new deltas are added to the diff. + +* `git_fetch_options` and `git_push_options` have gained a `custom_headers` + field to set the extra HTTP header fields to send. + +* `git_stream_register_tls()` lets you register a callback to be used + as the constructor for a TLS stream instead of the libgit2 built-in + one. + +* `git_commit_header_field()` allows you to look up a specific header + field in a commit. + +* `git_commit_extract_signature()` extracts the signature from a + commit and gives you both the signature and the signed data so you + can verify it. + +### API removals + +* No APIs were removed in this version. + +### Breaking API changes + +* The `git_merge_tree_flag_t` is now `git_merge_flag_t`. Subsequently, + its members are no longer prefixed with `GIT_MERGE_TREE_FLAG` but are + now prefixed with `GIT_MERGE_FLAG`, and the `tree_flags` field of the + `git_merge_options` structure is now named `flags`. + +* The `git_merge_file_flags_t` enum is now `git_merge_file_flag_t` for + consistency with other enum type names. + +* `git_cert` descendent types now have a proper `parent` member + +* It is the responsibility of the refdb backend to decide what to do + with the reflog on ref deletion. The file-based backend must delete + it, a database-backed one may wish to archive it. + +* `git_config_backend` has gained two entries. `lock` and `unlock` + with which to implement the transactional/atomic semantics for the + configuration backend. + +* `git_index_add` and `git_index_conflict_add()` will now use the case + as provided by the caller on case insensitive systems. Previous + versions would keep the case as it existed in the index. This does + not affect the higher-level `git_index_add_bypath` or + `git_index_add_frombuffer` functions. + +* The `notify_payload` field of `git_diff_options` was renamed to `payload` + to reflect that it's also the payload for the new progress callback. + +* The `git_config_level_t` enum has gained a higher-priority value + `GIT_CONFIG_LEVEL_PROGRAMDATA` which represent a rough Windows equivalent + to the system level configuration. + +* `git_rebase_init()` not also takes a merge options. + +* The index no longer performs locking itself. This is not something + users of the library should have been relying on as it's not part of + the concurrency guarantees. + v0.23 ------ @@ -239,8 +349,8 @@ v0.23 * `git_rebase_options` now contains a `git_checkout_options` struct that will be used for functions that modify the working directory, - namely `git_checkout_init`, `git_checkout_next` and - `git_checkout_abort`. As a result, `git_rebase_open` now also takes + namely `git_rebase_init`, `git_rebase_next` and + `git_rebase_abort`. As a result, `git_rebase_open` now also takes a `git_rebase_options` and only the `git_rebase_init` and `git_rebase_open` functions take a `git_rebase_options`, where they will persist the options to subsequent `git_rebase` calls. diff --git a/CMakeLists.txt b/CMakeLists.txt index 4747818fe..f4e56e6cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ CMAKE_POLICY(SET CMP0015 NEW) SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") INCLUDE(CheckLibraryExists) +INCLUDE(CheckFunctionExists) +INCLUDE(CheckStructHasMember) INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgConfig) @@ -39,6 +41,11 @@ OPTION( USE_SSH "Link with libssh to enable SSH support" ON ) OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF ) OPTION( VALGRIND "Configure build for valgrind" OFF ) OPTION( CURL "User curl for HTTP if available" ON) +OPTION( DEBUG_POOL "Enable debug pool allocator" OFF ) + +IF(DEBUG_POOL) + ADD_DEFINITIONS(-DGIT_DEBUG_POOL) +ENDIF() IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") SET( USE_ICONV ON ) @@ -59,6 +66,10 @@ IF(MSVC) # are linking statically OPTION( STATIC_CRT "Link the static CRT libraries" ON ) + # 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) @@ -80,6 +91,27 @@ 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" OFF ) +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. @@ -95,6 +127,23 @@ 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) @@ -115,13 +164,13 @@ FUNCTION(TARGET_OS_LIBRARIES target) ENDIF() ENDFUNCTION() -# For the MSVC IDE, this function splits up the source files like windows -# explorer does. This is esp. useful with the libgit2_clar project, were -# usually 2 or more files share the same name. Sadly, this file grouping -# is a per-directory option in cmake and not per-target, resulting in -# empty virtual folders "tests" for the git2.dll -FUNCTION(MSVC_SPLIT_SOURCES target) - IF(MSVC_IDE) +# 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 ".*/") @@ -152,8 +201,18 @@ STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "$ INCLUDE_DIRECTORIES(src include) IF (SECURITY_FOUND) - MESSAGE("-- Found Security ${SECURITY_DIRS}") - LIST(APPEND LIBGIT2_PC_LIBS "-framework Security") + # 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) @@ -162,6 +221,13 @@ IF (COREFOUNDATION_FOUND) 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) INCLUDE_DIRECTORIES(deps/http-parser) @@ -178,7 +244,7 @@ IF (WIN32 AND WINHTTP) SET(LIBWINHTTP_PATH "${CMAKE_CURRENT_BINARY_DIR}/deps/winhttp") FILE(MAKE_DIRECTORY ${LIBWINHTTP_PATH}) - IF ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + 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") @@ -200,7 +266,8 @@ IF (WIN32 AND WINHTTP) LINK_DIRECTORIES(${LIBWINHTTP_PATH}) ENDIF () - LINK_LIBRARIES(winhttp rpcrt4 crypt32) + LINK_LIBRARIES(winhttp rpcrt4 crypt32 ole32) + LIST(APPEND LIBGIT2_PC_LIBS "-lwinhttp" "-lrpcrt4" "-lcrypt32" "-lole32") ELSE () IF (CURL) PKG_CHECK_MODULES(CURL libcurl) @@ -286,7 +353,7 @@ IF (LIBSSH2_FOUND) #SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} ${LIBSSH2_LDFLAGS}") SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) - CHECK_LIBRARY_EXISTS("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "" HAVE_LIBSSH2_MEMORY_CREDENTIALS) + 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() @@ -336,6 +403,7 @@ IF (MSVC) IF (MSVC_CRTDBG) SET(CRT_FLAG_DEBUG "${CRT_FLAG_DEBUG} /DGIT_MSVC_CRTDBG") + SET(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES}" "Dbghelp.lib") ENDIF() # /Zi - Create debugging information @@ -430,6 +498,21 @@ ELSE () ENDIF () 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) @@ -461,6 +544,18 @@ IF (THREADSAFE) 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 @@ -490,7 +585,7 @@ ELSE() ENDIF() # Compile and link libgit2 -ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC}) +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}) @@ -505,7 +600,7 @@ IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") ENDIF() -MSVC_SPLIT_SOURCES(git2) +IDE_SPLIT_SOURCES(git2) IF (SONAME) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) @@ -536,7 +631,12 @@ INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) # Tests IF (BUILD_CLAR) - FIND_PACKAGE(PythonInterp REQUIRED) + 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") @@ -560,7 +660,7 @@ IF (BUILD_CLAR) ${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_SHA1}) + 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}) @@ -569,7 +669,7 @@ IF (BUILD_CLAR) TARGET_LINK_LIBRARIES(libgit2_clar ${GSSAPI_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${ICONV_LIBRARIES}) TARGET_OS_LIBRARIES(libgit2_clar) - MSVC_SPLIT_SOURCES(libgit2_clar) + IDE_SPLIT_SOURCES(libgit2_clar) IF (MSVC_IDE) # Precompiled headers @@ -582,6 +682,10 @@ IF (BUILD_CLAR) ELSE () ADD_TEST(libgit2_clar libgit2_clar -v) 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) ENDIF () IF (TAGS) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0a0e4ebab --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [libgit2@gmail.com][email]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[email]: mailto:libgit2@gmail.com +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 5b8238a78..0be4b33cc 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -3,6 +3,38 @@ We like to keep the source consistent and readable. Herein are some guidelines that should help with that. +## External API + +We have a few rules to avoid surprising ways of calling functions and +some rules for consumers of the library to avoid stepping on each +other's toes. + + - Property accessors return the value directly (e.g. an `int` or + `const char *`) but if a function can fail, we return a `int` value + and the output parameters go first in the parameter list, followed + by the object that a function is operating on, and then any other + arguments the function may need. + + - If a function returns an object as a return value, that function is + a getter and the object's lifetime is tied to the parent + object. Objects which are returned as the first argument as a + pointer-to-pointer are owned by the caller and it is repsponsible + for freeing it. Strings are returned via `git_buf` in order to + allow for re-use and safe freeing. + + - Most of what libgit2 does relates to I/O so you as a general rule + you should assume that any function can fail due to errors as even + getting data from the filesystem can result in all sorts of errors + and complex failure cases. + + - Paths inside the Git system are separated by a slash (0x2F). If a + function accepts a path on disk, then backslashes (0x5C) are also + accepted on Windows. + + - Do not mix allocators. If something has been allocated by libgit2, + you do not know which is the right free function in the general + case. Use the free functions provided for each object type. + ## Compatibility `libgit2` runs on many different platforms with many different compilers. diff --git a/COPYING b/COPYING index 6f6115e3b..1b88b9b8e 100644 --- a/COPYING +++ b/COPYING @@ -407,6 +407,52 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------------------------------------------------------------- The regex library (deps/regex/) is licensed under the GNU LGPL +(available at the end of this file). + +Definitions for data structures and routines for the regular +expression library. + +Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008 +Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with the GNU C Library; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +---------------------------------------------------------------------- + +The bundled winhttp definition files (deps/winhttp/) are licensed under +the GNU LGPL (available at the end of this file). + +Copyright (C) 2007 Francois Gouget + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +---------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 diff --git a/PROJECTS.md b/PROJECTS.md index 4f200b7f9..87ce78f02 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -48,7 +48,7 @@ These are good small projects to get started with libgit2. a new example that mirrors the behavior. Examples don't have to be perfect emulations, but should demonstrate how to use the libgit2 APIs to get results that are similar to Git commands. This lets you (and us) - easily exercise a particular facet of the API and measure compatability + easily exercise a particular facet of the API and measure compatibility and feature parity with core git. * Submit a PR to clarify documentation! While we do try to document all of the APIs, your fresh eyes on the documentation will find areas that are @@ -75,8 +75,6 @@ might make good smaller projects by themselves. * Extract the Git tests that exercise that command * Convert the tests to call our emulation * These tests could go in examples/tests/... -* Fix symlink support for files in the .git directory (i.e. don't overwrite - the symlinks when writing the file contents back out) * Add hooks API to enumerate and manage hooks (not run them at this point) * Enumeration of available hooks * Lookup API to see which hooks have a script and get the script @@ -85,8 +83,6 @@ might make good smaller projects by themselves. executes the action in question * Isolate logic of ignore evaluation into a standalone API * Upgrade internal libxdiff code to latest from core Git -* Improve index internals with hashtable lookup for files instead of - using binary search every time * Tree builder improvements: * Extend to allow building a tree hierarchy * Apply-patch API diff --git a/README.md b/README.md index 3191aeee2..8ea787b3e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Additionally, the example code has been released to the public domain (see the * Website: [libgit2.github.com](http://libgit2.github.com) * StackOverflow Tag: [libgit2](http://stackoverflow.com/questions/tagged/libgit2) * Issues: [GitHub Issues](https://github.com/libgit2/libgit2/issues) (Right here!) -* API documentation: +* API documentation: * IRC: [#libgit2](irc://irc.freenode.net/libgit2) on irc.freenode.net. * Mailing list: The libgit2 mailing list was traditionally hosted in Librelist but has been deprecated. We encourage you to @@ -80,6 +80,12 @@ Threading See [THREADING](THREADING.md) for information +Conventions +=========== + +See [CONVENTIONS](CONVENTIONS.md) for an overview of the external +and internal API/coding conventions we use. + Building libgit2 - Using CMake ============================== @@ -88,7 +94,7 @@ Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthrea they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API for threading. -The `libgit2` library is built using [CMake]() (version 2.8 or newer) on all platforms. +The `libgit2` library is built using [CMake]() (version 2.8 or newer) on all platforms. On most systems you can build the library using the following commands @@ -103,7 +109,7 @@ To install the library you can specify the install prefix by setting: $ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix $ cmake --build . --target install -For more advanced use or questions about CMake please read . +For more advanced use or questions about CMake please read . The following CMake variables are declared: @@ -141,7 +147,7 @@ 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](https://libgit2.github.com/docs/guides/build-and-link) +See [the website](http://libgit2.github.com/docs/guides/build-and-link/) for more detailed instructions. Android @@ -184,9 +190,9 @@ Here are the bindings to libgit2 that are currently available: * Go * git2go * GObject - * libgit2-glib + * libgit2-glib * Haskell - * hgit2 + * hgit2 * Java * Jagged * Julia @@ -197,7 +203,7 @@ Here are the bindings to libgit2 that are currently available: * libgit2sharp * Node.js * node-gitteh - * nodegit + * nodegit * Objective-C * objective-git * OCaml @@ -230,7 +236,7 @@ How Can I Contribute? ================================== Check the [contribution guidelines](CONTRIBUTING.md) to understand our -workflow, the libgit2 [coding conventions](CONVENTIONS.md), and out list of +workflow, the libgit2 [coding conventions](CONVENTIONS.md), and our list of [good starting projects](PROJECTS.md). License diff --git a/THREADING.md b/THREADING.md index 3717d6c88..0b9e50286 100644 --- a/THREADING.md +++ b/THREADING.md @@ -72,13 +72,19 @@ which locking function it should use. This means that libgit2 cannot know what to set as the user of libgit2 may use OpenSSL independently and the locking settings must survive libgit2 shutting down. +Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used +by libssh2 depending on the configuration. If OpenSSL is used both by +libgit2 and libssh2, you only need to set up threading for OpenSSL once. + libgit2 does provide a last-resort convenience function `git_openssl_set_locking()` (available in `sys/openssl.h`) to use the platform-native mutex mechanisms to perform the locking, which you may rely on if you do not want to use OpenSSL outside of libgit2, or you know that libgit2 will outlive the rest of the operations. It is not safe to use OpenSSL multi-threaded after libgit2's shutdown function -has been called. +has been called. Note `git_openssl_set_locking()` only works if +libgit2 uses OpenSSL directly - if OpenSSL is only used as a dependency +of libssh2 as described above, `git_openssl_set_locking()` is a no-op. If your programming language offers a package/bindings for OpenSSL, you should very strongly prefer to use that in order to set up @@ -87,14 +93,14 @@ when using this function. See the [OpenSSL documentation](https://www.openssl.org/docs/crypto/threads.html) -on threading for more details. +on threading for more details, and http://trac.libssh2.org/wiki/MultiThreading +for a specific example of providing the threading callbacks. Be also aware that libgit2 does not always link against OpenSSL if there are alternatives provided by the system. -libssh2 may be linked against OpenSSL or libgcrypt. If it uses -OpenSSL, you only need to set up threading for OpenSSL once and the -above paragraphs are enough. If it uses libgcrypt, then you need to +libssh2 may be linked against OpenSSL or libgcrypt. If it uses OpenSSL, +see the above paragraphs. If it uses libgcrypt, then you need to set up its locking before using it multi-threaded. libgit2 has no direct connection to libgcrypt and thus has not convenience functions for it (but libgcrypt has macros). Read libgcrypt's diff --git a/appveyor.yml b/appveyor.yml index 166fa56b1..3ed3c49a1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,4 +36,8 @@ build_script: - cmd: | if "%GENERATOR%"=="MSYS Makefiles" (C:\MinGW\msys\1.0\bin\sh --login /c/projects/libgit2/script/appveyor-mingw.sh) test_script: -- ps: ctest -V . +- ps: | + 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 diff --git a/docs/error-handling.md b/docs/error-handling.md index 2dbe64a71..719244d2f 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -128,7 +128,7 @@ The public error API bugs, but in the meantime, please code defensively and check for NULL when calling this function. -- `void geterr_clear(void)`: This function clears the last error. The +- `void giterr_clear(void)`: This function clears the last error. The library will call this when an error is generated by low level function and the higher level function handles the error. diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 6be12406b..177359b88 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -23,32 +23,6 @@ static int progress_cb(const char *str, int len, void *data) return 0; } -static void *download(void *ptr) -{ - struct dl_data *data = (struct dl_data *)ptr; - - // Connect to the remote end specifying that we want to fetch - // information from it. - if (git_remote_connect(data->remote, GIT_DIRECTION_FETCH, &data->fetch_opts->callbacks) < 0) { - data->ret = -1; - goto exit; - } - - // Download the packfile and index it. This function updates the - // amount of received data and the indexer stats which lets you - // inform the user about progress. - if (git_remote_download(data->remote, NULL, data->fetch_opts) < 0) { - data->ret = -1; - goto exit; - } - - data->ret = 0; - -exit: - data->finished = 1; - return &data->ret; -} - /** * This function gets called for each remote-tracking branch that gets * updated. The message we output depends on whether it's a new one or @@ -73,6 +47,25 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo return 0; } +/** + * This gets called during the download and indexing. Here we show + * processed and total objects in the pack and the amount of received + * data. Most frontends will probably want to show a percentage and + * the download rate. + */ +static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) +{ + if (stats->received_objects == stats->total_objects) { + printf("Resolving deltas %d/%d\r", + stats->indexed_deltas, stats->total_deltas); + } else if (stats->total_objects > 0) { + printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r", + stats->received_objects, stats->total_objects, + stats->indexed_objects, stats->received_bytes); + } + return 0; +} + /** Entry point for this command */ int fetch(git_repository *repo, int argc, char **argv) { @@ -80,9 +73,6 @@ int fetch(git_repository *repo, int argc, char **argv) const git_transfer_progress *stats; struct dl_data data; git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; -#ifndef _WIN32 - pthread_t worker; -#endif if (argc < 2) { fprintf(stderr, "usage: %s fetch \n", argv[-1]); @@ -99,49 +89,23 @@ int fetch(git_repository *repo, int argc, char **argv) // Set up the callbacks (only update_tips for now) fetch_opts.callbacks.update_tips = &update_cb; fetch_opts.callbacks.sideband_progress = &progress_cb; + fetch_opts.callbacks.transfer_progress = transfer_progress_cb; fetch_opts.callbacks.credentials = cred_acquire_cb; - // Set up the information for the background worker thread - data.remote = remote; - data.fetch_opts = &fetch_opts; - data.ret = 0; - data.finished = 0; - - stats = git_remote_stats(remote); - -#ifdef _WIN32 - download(&data); -#else - pthread_create(&worker, NULL, download, &data); - - // Loop while the worker thread is still running. Here we show processed - // and total objects in the pack and the amount of received - // data. Most frontends will probably want to show a percentage and - // the download rate. - do { - usleep(10000); - - if (stats->received_objects == stats->total_objects) { - printf("Resolving deltas %d/%d\r", - stats->indexed_deltas, stats->total_deltas); - } else if (stats->total_objects > 0) { - printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r", - stats->received_objects, stats->total_objects, - stats->indexed_objects, stats->received_bytes); - } - } while (!data.finished); - - if (data.ret < 0) - goto on_error; - - pthread_join(worker, NULL); -#endif + /** + * Perform the fetch with the configured refspecs from the + * config. Update the reflog for the updated references with + * "fetch". + */ + if (git_remote_fetch(remote, NULL, &fetch_opts, "fetch") < 0) + return -1; /** * If there are local objects (we got a thin pack), then tell * the user how many objects we saved from having to cross the * network. */ + stats = git_remote_stats(remote); if (stats->local_objects > 0) { printf("\rReceived %d/%d objects in %" PRIuZ " bytes (used %d local objects)\n", stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); @@ -150,16 +114,6 @@ int fetch(git_repository *repo, int argc, char **argv) stats->indexed_objects, stats->total_objects, stats->received_bytes); } - // Disconnect the underlying connection to prevent from idling. - git_remote_disconnect(remote); - - // Update the references in the remote's namespace to point to the - // right commits. This may be needed even if there was no packfile - // to download, which can happen e.g. when the branches have been - // changed but all the needed objects are available locally. - if (git_remote_update_tips(remote, &fetch_opts.callbacks, 1, fetch_opts.download_tags, NULL) < 0) - return -1; - git_remote_free(remote); return 0; diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 21026562f..c9da79f5f 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -26,7 +26,7 @@ static int use_remote(git_repository *repo, char *name) */ callbacks.credentials = cred_acquire_cb; - error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks); + error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL); if (error < 0) goto cleanup; diff --git a/git.git-authors b/git.git-authors index 9131a1fa1..6a85224b4 100644 --- a/git.git-authors +++ b/git.git-authors @@ -39,6 +39,7 @@ ok Adam Simpkins (http transport) ok Adrian Johnson ok Alexey Shumkin ok Andreas Ericsson +ok Antoine Pelisse ok Boyd Lynn Gerber ok Brandon Casey ok Brian Downing diff --git a/include/git2/blame.h b/include/git2/blame.h index 173e9994b..84bb7f94c 100644 --- a/include/git2/blame.h +++ b/include/git2/blame.h @@ -74,8 +74,8 @@ typedef struct git_blame_options { uint16_t min_match_characters; git_oid newest_commit; git_oid oldest_commit; - uint32_t min_line; - uint32_t max_line; + size_t min_line; + size_t max_line; } git_blame_options; #define GIT_BLAME_OPTIONS_VERSION 1 @@ -113,15 +113,15 @@ GIT_EXTERN(int) git_blame_init_options( * root, or the commit specified in git_blame_options.oldest_commit) */ typedef struct git_blame_hunk { - uint16_t lines_in_hunk; + size_t lines_in_hunk; git_oid final_commit_id; - uint16_t final_start_line_number; + size_t final_start_line_number; git_signature *final_signature; git_oid orig_commit_id; const char *orig_path; - uint16_t orig_start_line_number; + size_t orig_start_line_number; git_signature *orig_signature; char boundary; @@ -156,7 +156,7 @@ GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex( */ GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline( git_blame *blame, - uint32_t lineno); + size_t lineno); /** * Get the blame for a single file. diff --git a/include/git2/blob.h b/include/git2/blob.h index 4a6d8e50a..9a57c37f5 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -171,8 +171,8 @@ typedef int (*git_blob_chunk_cb)(char *content, size_t max_length, void *payload * - The `callback` must return the number of bytes that have been * written to the `content` buffer. * - * - When there is no more data to stream, `callback` should return - * 0. This will prevent it from being invoked anymore. + * - When there is no more data to stream, `callback` should return 0. + * This will prevent it from being invoked anymore. * * - If an error occurs, the callback should return a negative value. * This value will be returned to the caller. diff --git a/include/git2/commit.h b/include/git2/commit.h index 04711c1fa..3488c7440 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -127,6 +127,19 @@ GIT_EXTERN(const char *) git_commit_message_raw(const git_commit *commit); */ GIT_EXTERN(const char *) git_commit_summary(git_commit *commit); +/** + * Get the long "body" of the git commit message. + * + * The returned message is the body of the commit, comprising + * everything but the first paragraph of the message. Leading and + * trailing whitespaces are trimmed. + * + * @param commit a previously loaded commit. + * @return the body of a commit or NULL when no the message only + * consists of a summary + */ +GIT_EXTERN(const char *) git_commit_body(git_commit *commit); + /** * Get the commit time (i.e. committer time) of a commit. * @@ -250,6 +263,24 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor( */ GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field); +/** + * Extract the signature from a commit + * + * If the id is not for a commit, the error class will be + * `GITERR_INVALID`. If the commit does not have a signature, the + * error class will be `GITERR_OBJECT`. + * + * @param signature the signature block + * @param signed_data signed data; this is the commit contents minus the signature block + * @param repo the repository in which the commit exists + * @param commit_id the commit from which to extract the data + * @param field the name of the header field containing the signature + * block; pass `NULL` to extract the default 'gpgsig' + * @return 0 on success, GIT_ENOTFOUND if the id is not for a commit + * or the commit does not have a signature. + */ +GIT_EXTERN(int) git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field); + /** * Create new commit in the repository from a list of `git_object` pointers * diff --git a/include/git2/common.h b/include/git2/common.h index d84a76512..4f43185f8 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -10,12 +10,6 @@ #include #include -#ifdef _MSC_VER -# include "inttypes.h" -#else -# include -#endif - #ifdef __cplusplus # define GIT_BEGIN_DECL extern "C" { # define GIT_END_DECL } @@ -26,6 +20,14 @@ # define GIT_END_DECL /* empty */ #endif +#if defined(_MSC_VER) && _MSC_VER < 1800 + GIT_BEGIN_DECL +# include "inttypes.h" + GIT_END_DECL +#else +# include +#endif + /** Declare a public function exported for application use. */ #if __GNUC__ >= 4 # define GIT_EXTERN(type) extern \ @@ -99,8 +101,9 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); */ typedef enum { GIT_FEATURE_THREADS = (1 << 0), - GIT_FEATURE_HTTPS = (1 << 1), - GIT_FEATURE_SSH = (1 << 2), + GIT_FEATURE_HTTPS = (1 << 1), + GIT_FEATURE_SSH = (1 << 2), + GIT_FEATURE_NSEC = (1 << 3), } git_feature_t; /** @@ -143,6 +146,8 @@ typedef enum { GIT_OPT_GET_TEMPLATE_PATH, GIT_OPT_SET_TEMPLATE_PATH, GIT_OPT_SET_SSL_CERT_LOCATIONS, + GIT_OPT_SET_USER_AGENT, + GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, } git_libgit2_opt_t; /** @@ -170,9 +175,9 @@ typedef enum { * * opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf) * * > Get the search path for a given level of config data. "level" must - * > be one of `GIT_CONFIG_LEVEL_SYSTEM`, `GIT_CONFIG_LEVEL_GLOBAL`, or - * > `GIT_CONFIG_LEVEL_XDG`. The search path is written to the `out` - * > buffer. + * > be one of `GIT_CONFIG_LEVEL_SYSTEM`, `GIT_CONFIG_LEVEL_GLOBAL`, + * > `GIT_CONFIG_LEVEL_XDG`, or `GIT_CONFIG_LEVEL_PROGRAMDATA`. + * > The search path is written to the `out` buffer. * * * opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path) * @@ -184,8 +189,9 @@ typedef enum { * > variables). Use magic path `$PATH` to include the old value * > of the path (if you want to prepend or append, for instance). * > - * > - `level` must be GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_GLOBAL, - * > or GIT_CONFIG_LEVEL_XDG. + * > - `level` must be `GIT_CONFIG_LEVEL_SYSTEM`, + * > `GIT_CONFIG_LEVEL_GLOBAL`, `GIT_CONFIG_LEVEL_XDG`, or + * > `GIT_CONFIG_LEVEL_PROGRAMDATA`. * * * opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, git_otype type, size_t size) * @@ -238,6 +244,22 @@ typedef enum { * > * > Either parameter may be `NULL`, but not both. * + * * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent) + * + * > Set the value of the User-Agent header. This value will be + * > appended to "git/1.0", for compatibility with other git clients. + * > + * > - `user_agent` is the value that will be delivered as the + * > User-Agent header on HTTP requests. + * + * * opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, int enabled) + * + * > Enable strict input validation when creating new objects + * > to ensure that all inputs to the new objects are valid. For + * > example, when this is enabled, the parent(s) and tree inputs + * > will be validated when creating a new commit. This defaults + * > to disabled. + * * @param option Option key * @param ... value to set the option * @return 0 on success, <0 on failure diff --git a/include/git2/config.h b/include/git2/config.h index 6d3fdb0c2..d0f1ba1b3 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -29,25 +29,28 @@ GIT_BEGIN_DECL * priority levels as well. */ typedef enum { + /** System-wide on Windows, for compatibility with portable git */ + GIT_CONFIG_LEVEL_PROGRAMDATA = 1, + /** System-wide configuration file; /etc/gitconfig on Linux systems */ - GIT_CONFIG_LEVEL_SYSTEM = 1, + GIT_CONFIG_LEVEL_SYSTEM = 2, /** XDG compatible configuration file; typically ~/.config/git/config */ - GIT_CONFIG_LEVEL_XDG = 2, + GIT_CONFIG_LEVEL_XDG = 3, /** User-specific configuration file (also called Global configuration * file); typically ~/.gitconfig */ - GIT_CONFIG_LEVEL_GLOBAL = 3, + GIT_CONFIG_LEVEL_GLOBAL = 4, /** Repository specific configuration file; $WORK_DIR/.git/config on * non-bare repos */ - GIT_CONFIG_LEVEL_LOCAL = 4, + GIT_CONFIG_LEVEL_LOCAL = 5, /** Application specific configuration file; freely defined by applications */ - GIT_CONFIG_LEVEL_APP = 5, + GIT_CONFIG_LEVEL_APP = 6, /** Represents the highest level available config file (i.e. the most * specific config file available that actually is loaded) @@ -141,6 +144,17 @@ GIT_EXTERN(int) git_config_find_xdg(git_buf *out); */ GIT_EXTERN(int) git_config_find_system(git_buf *out); +/** + * Locate the path to the configuration file in ProgramData + * + * Look for the file in %PROGRAMDATA%\Git\config used by portable git. + * + * @param out Pointer to a user-allocated git_buf in which to store the path + * @return 0 if a ProgramData configuration file has been + * found. Its path will be stored in `out`. + */ +GIT_EXTERN(int) git_config_find_programdata(git_buf *out); + /** * Open the global, XDG and system configuration files * @@ -171,6 +185,9 @@ GIT_EXTERN(int) git_config_new(git_config **out); * parsed; it's expected to be a native Git config file following * the default Git config syntax (see man git-config). * + * If the file does not exist, the file will still be added and it + * will be created the first time we write to it. + * * Note that the configuration object will free the file * automatically. * @@ -202,8 +219,7 @@ GIT_EXTERN(int) git_config_add_file_ondisk( * * @param out The configuration instance to create * @param path Path to the on-disk file to open - * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist - * or an error code + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path); @@ -689,6 +705,24 @@ GIT_EXTERN(int) git_config_backend_foreach_match( void *payload); +/** + * Lock the backend with the highest priority + * + * Locking disallows anybody else from writing to that backend. Any + * updates made after locking will not be visible to a reader until + * the file is unlocked. + * + * You can apply the changes by calling `git_transaction_commit()` + * before freeing the transaction. Either of these actions will unlock + * the config. + * + * @param tx the resulting transaction, use this to commit or undo the + * changes + * @param cfg the configuration in which to lock + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_lock(git_transaction **tx, git_config *cfg); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h index 840c22536..1416d5642 100644 --- a/include/git2/cred_helpers.h +++ b/include/git2/cred_helpers.h @@ -34,7 +34,7 @@ typedef struct git_cred_userpass_payload { * * @param cred The newly created credential object. * @param url The resource for which we are demanding a credential. - * @param user_from_url The username that was embedded in a "user@host" + * @param user_from_url The username that was embedded in a "user\@host" * remote url, or NULL if not included. * @param allowed_types A bitmask stating which cred types are OK to return. * @param payload The payload provided when specifying this callback. (This is diff --git a/include/git2/diff.h b/include/git2/diff.h index b3ab5397e..c35701a46 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -129,8 +129,12 @@ typedef enum { */ GIT_DIFF_INCLUDE_CASECHANGE = (1u << 11), - /** If the pathspec is set in the diff options, this flags means to - * apply it as an exact match instead of as an fnmatch pattern. + /** If the pathspec is set in the diff options, this flags indicates + * that the paths will be treated as literal paths instead of + * fnmatch patterns. Each path in the list must either be a full + * path to a file or a directory. (A trailing slash indicates that + * the path will _only_ match a directory). If a directory is + * specified, all children will be included. */ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12), @@ -346,6 +350,22 @@ typedef int (*git_diff_notify_cb)( const char *matched_pathspec, void *payload); +/** + * Diff progress callback. + * + * Called before each file comparison. + * + * @param diff_so_far The diff being generated. + * @param old_path The path to the old file or NULL. + * @param new_path The path to the new file or NULL. + * @return Non-zero to abort the diff. + */ +typedef int (*git_diff_progress_cb)( + const git_diff *diff_so_far, + const char *old_path, + const char *new_path, + void *payload); + /** * Structure describing options about how the diff should be executed. * @@ -366,8 +386,10 @@ typedef int (*git_diff_notify_cb)( * - `max_size` is a file size (in bytes) above which a blob will be marked * as binary automatically; pass a negative value to disable. * - `notify_cb` is an optional callback function, notifying the consumer of - * which files are being examined as the diff is generated - * - `notify_payload` is the payload data to pass to the `notify_cb` function + * changes to the diff as new deltas are added. + * - `progress_cb` is an optional callback function, notifying the consumer of + * which files are being examined as the diff is generated. + * - `payload` is the payload to pass to the callback functions. * - `ignore_submodules` overrides the submodule ignore setting for all * submodules in the diff. */ @@ -379,8 +401,9 @@ typedef struct { git_submodule_ignore_t ignore_submodules; /**< submodule ignore rule */ git_strarray pathspec; /**< defaults to include all paths */ - git_diff_notify_cb notify_cb; - void *notify_payload; + git_diff_notify_cb notify_cb; + git_diff_progress_cb progress_cb; + void *payload; /* options controlling how to diff text is generated */ @@ -399,7 +422,7 @@ typedef struct { * `git_diff_options_init` programmatic initialization. */ #define GIT_DIFF_OPTIONS_INIT \ - {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL,0}, NULL, NULL, 3} + {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL,0}, NULL, NULL, NULL, 3} /** * Initializes a `git_diff_options` with default values. Equivalent to @@ -835,6 +858,25 @@ GIT_EXTERN(int) git_diff_tree_to_workdir_with_index( git_tree *old_tree, const git_diff_options *opts); /**< can be NULL for defaults */ +/** + * Create a diff with the difference between two index objects. + * + * The first index will be used for the "old_file" side of the delta and the + * second index will be used for the "new_file" side of the delta. + * + * @param diff Output pointer to a git_diff pointer to be allocated. + * @param repo The repository containing the indexes. + * @param old_index A git_index object to diff from. + * @param new_index A git_index object to diff to. + * @param opts Structure with options to influence diff or NULL for defaults. + */ +GIT_EXTERN(int) git_diff_index_to_index( + git_diff **diff, + git_repository *repo, + git_index *old_index, + git_index *new_index, + const git_diff_options *opts); /**< can be NULL for defaults */ + /** * Merge one diff into another. * @@ -1152,7 +1194,7 @@ typedef enum { } git_diff_stats_format_t; /** - * Accumlate diff statistics for all patches. + * Accumulate diff statistics for all patches. * * @param out Structure containg the diff statistics. * @param diff A git_diff generated by one of the above functions. @@ -1244,12 +1286,15 @@ typedef struct { /** Summary of the change */ const char *summary; + /** Commit message's body */ + const char *body; + /** Author of the change */ const git_signature *author; } git_diff_format_email_options; #define GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION 1 -#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL} +#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL, NULL} /** * Create an e-mail ready patch from a diff. diff --git a/include/git2/errors.h b/include/git2/errors.h index e189e55f1..3ecea34bf 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -49,6 +49,7 @@ typedef enum { GIT_EINVALID = -21, /**< Invalid operation or input */ GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */ GIT_EDIRECTORY = -23, /**< The operation is not valid for a directory */ + GIT_EMERGECONFLICT = -24, /**< A merge conflict exists and cannot continue */ GIT_PASSTHROUGH = -30, /**< Internal only */ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ @@ -113,18 +114,6 @@ GIT_EXTERN(const git_error *) giterr_last(void); */ GIT_EXTERN(void) giterr_clear(void); -/** - * Get the last error data and clear it. - * - * This copies the last error into the given `git_error` struct - * and returns 0 if the copy was successful, leaving the error - * cleared as if `giterr_clear` had been called. - * - * If there was no existing error in the library, -1 will be returned - * and the contents of `cpy` will be left unmodified. - */ -GIT_EXTERN(int) giterr_detach(git_error *cpy); - /** * Set the error message string for this thread. * @@ -137,11 +126,6 @@ GIT_EXTERN(int) giterr_detach(git_error *cpy); * This error message is stored in thread-local storage and only applies * to the particular thread that this libgit2 call is made from. * - * NOTE: Passing the `error_class` as GITERR_OS has a special behavior: we - * attempt to append the system default error message for the last OS error - * that occurred and then clear the last error. The specific implementation - * of looking up and clearing this last OS error will vary by platform. - * * @param error_class One of the `git_error_t` enum above describing the * general subsystem that is responsible for the error. * @param string The formatted error message to keep diff --git a/include/git2/index.h b/include/git2/index.h index 7caf3ed78..466765be3 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -154,13 +154,27 @@ typedef enum { GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2), } git_index_add_option_t; -/** - * Match any index stage. - * - * Some index APIs take a stage to match; pass this value to match - * any entry matching the path regardless of stage. - */ -#define GIT_INDEX_STAGE_ANY -1 +typedef enum { + /** + * Match any index stage. + * + * Some index APIs take a stage to match; pass this value to match + * any entry matching the path regardless of stage. + */ + GIT_INDEX_STAGE_ANY = -1, + + /** A normal staged file in the index. */ + GIT_INDEX_STAGE_NORMAL = 0, + + /** The ancestor side of a conflict. */ + GIT_INDEX_STAGE_ANCESTOR = 1, + + /** The "ours" side of a conflict. */ + GIT_INDEX_STAGE_OURS = 2, + + /** The "theirs" side of a conflict. */ + GIT_INDEX_STAGE_THEIRS = 3, +} git_index_stage_t; /** @name Index File Functions * @@ -643,6 +657,17 @@ GIT_EXTERN(int) git_index_update_all( */ GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *path); +/** + * Find the first position of any entries matching a prefix. To find the first position + * of a path inside a given folder, suffix the prefix with a '/'. + * + * @param at_pos the address to which the position of the index entry is written (optional) + * @param index an existing index object + * @param prefix the prefix to search for + * @return 0 with valid value in at_pos; an error code otherwise + */ +GIT_EXTERN(int) git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix); + /**@}*/ /** @name Conflict Index Entry Functions diff --git a/include/git2/merge.h b/include/git2/merge.h index 5fef452b9..560797a0c 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -62,8 +62,8 @@ GIT_EXTERN(int) git_merge_file_init_input( unsigned int version); /** - * Flags for `git_merge_tree` options. A combination of these flags can be - * passed in via the `tree_flags` value in the `git_merge_options`. + * Flags for `git_merge` options. A combination of these flags can be + * passed in via the `flags` value in the `git_merge_options`. */ typedef enum { /** @@ -71,8 +71,28 @@ typedef enum { * side or the common ancestor and the "theirs" side. This will enable * the ability to merge between a modified and renamed file. */ - GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), -} git_merge_tree_flag_t; + GIT_MERGE_FIND_RENAMES = (1 << 0), + + /** + * If a conflict occurs, exit immediately instead of attempting to + * continue resolving conflicts. The merge operation will fail with + * GIT_EMERGECONFLICT and no index will be returned. + */ + GIT_MERGE_FAIL_ON_CONFLICT = (1 << 1), + + /** + * Do not write the REUC extension on the generated index + */ + GIT_MERGE_SKIP_REUC = (1 << 2), + + /** + * If the commits being merged have multiple merge bases, do not build + * a recursive merge base (by merging the multiple merge bases), + * instead simply use the first base. This flag provides a similar + * merge base to `git-merge-resolve`. + */ + GIT_MERGE_NO_RECURSIVE = (1 << 3), +} git_merge_flag_t; /** * Merge file favor options for `git_merge_options` instruct the file-level @@ -140,7 +160,7 @@ typedef enum { /** Take extra time to find minimal diff */ GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), -} git_merge_file_flags_t; +} git_merge_file_flag_t; /** * Options for merging a file @@ -169,8 +189,8 @@ typedef struct { /** The file to favor in region conflicts. */ git_merge_file_favor_t favor; - /** see `git_merge_file_flags_t` above */ - unsigned int flags; + /** see `git_merge_file_flag_t` above */ + git_merge_file_flag_t flags; } git_merge_file_options; #define GIT_MERGE_FILE_OPTIONS_VERSION 1 @@ -220,11 +240,13 @@ typedef struct { */ typedef struct { unsigned int version; - git_merge_tree_flag_t tree_flags; + + /** See `git_merge_flag_t` above */ + git_merge_flag_t flags; /** * Similarity to consider a file renamed (default 50). If - * `GIT_MERGE_TREE_FIND_RENAMES` is enabled, added files will be compared + * `GIT_MERGE_FIND_RENAMES` is enabled, added files will be compared * with deleted files to determine their similarity. Files that are * more similar than the rename threshold (percentage-wise) will be * treated as a rename. @@ -243,11 +265,19 @@ typedef struct { /** Pluggable similarity metric; pass NULL to use internal metric */ git_diff_similarity_metric *metric; + /** + * Maximum number of times to merge common ancestors to build a + * virtual merge base when faced with criss-cross merges. When this + * limit is reached, the next ancestor will simply be used instead of + * attempting to merge it. The default is unlimited. + */ + unsigned int recursion_limit; + /** Flags for handling conflicting content. */ git_merge_file_favor_t file_favor; - /** see `git_merge_file_flags_t` above */ - unsigned int file_flags; + /** see `git_merge_file_flag_t` above */ + git_merge_file_flag_t file_flags; } git_merge_options; #define GIT_MERGE_OPTIONS_VERSION 1 @@ -497,10 +527,6 @@ GIT_EXTERN(int) git_merge_trees( * or checked out. If the index is to be converted to a tree, the caller * should resolve any conflicts that arose as part of the merge. * - * The merge performed uses the first common ancestor, unlike the - * `git-merge-recursive` strategy, which may produce an artificial common - * ancestor tree when there are multiple ancestors. - * * The returned index must be freed explicitly with `git_index_free`. * * @param out pointer to store the index result in @@ -523,10 +549,6 @@ GIT_EXTERN(int) git_merge_commits( * to the index. Callers should inspect the repository's index after this * completes, resolve any conflicts and prepare a commit. * - * The merge performed uses the first common ancestor, unlike the - * `git-merge-recursive` strategy, which may produce an artificial common - * ancestor tree when there are multiple ancestors. - * * For compatibility with git, the repository is put into a merging * state. Once the commit is done (or if the uses wishes to abort), * you should clear this state by calling diff --git a/include/git2/rebase.h b/include/git2/rebase.h index d9aa175c7..9b9065ee4 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -38,19 +38,33 @@ typedef struct { */ int quiet; + /** + * Used by `git_rebase_init`, this will begin an in-memory rebase, + * which will allow callers to step through the rebase operations and + * commit the rebased changes, but will not rewind HEAD or update the + * repository to be in a rebasing state. This will not interfere with + * the working directory (if there is one). + */ + int inmemory; + /** * Used by `git_rebase_finish`, this is the name of the notes reference * used to rewrite notes for rebased commits when finishing the rebase; - * if NULL, the contents of the coniguration option `notes.rewriteRef` + * if NULL, the contents of the configuration option `notes.rewriteRef` * is examined, unless the configuration option `notes.rewrite.rebase` * is set to false. If `notes.rewriteRef` is also NULL, notes will * not be rewritten. */ const char *rewrite_notes_ref; + /** + * Options to control how trees are merged during `git_rebase_next`. + */ + git_merge_options merge_options; + /** * Options to control how files are written during `git_rebase_init`, - * `git_checkout_next` and `git_checkout_abort`. Note that a minimum + * `git_rebase_next` and `git_rebase_abort`. Note that a minimum * strategy of `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, * and a minimum strategy of `GIT_CHECKOUT_FORCE` is defaulted in * `abort` to match git semantics. @@ -101,7 +115,8 @@ typedef enum { #define GIT_REBASE_OPTIONS_VERSION 1 #define GIT_REBASE_OPTIONS_INIT \ - {GIT_REBASE_OPTIONS_VERSION, 0, NULL, GIT_CHECKOUT_OPTIONS_INIT} + { GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \ + GIT_CHECKOUT_OPTIONS_INIT} /** Indicates that a rebase operation is not (yet) in progress. */ #define GIT_REBASE_NO_OPERATION SIZE_MAX @@ -226,6 +241,21 @@ GIT_EXTERN(int) git_rebase_next( git_rebase_operation **operation, git_rebase *rebase); +/** + * Gets the index produced by the last operation, which is the result + * of `git_rebase_next` and which will be committed by the next + * invocation of `git_rebase_commit`. This is useful for resolving + * conflicts in an in-memory rebase before committing them. You must + * call `git_index_free` when you are finished with this. + * + * This is only applicable for in-memory rebases; for rebases within + * a working directory, the changes were applied to the repository's + * index. + */ +GIT_EXTERN(int) git_rebase_inmemory_index( + git_index **index, + git_rebase *rebase); + /** * Commits the current patch. You must have resolved any conflicts that * were introduced during the patch application from the `git_rebase_next` diff --git a/include/git2/remote.h b/include/git2/remote.h index 444fe5276..c42d96710 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -241,9 +241,10 @@ GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, * @param direction GIT_DIRECTION_FETCH if you want to fetch or * GIT_DIRECTION_PUSH if you want to push * @param callbacks the callbacks to use for this connection + * @param custom_headers extra HTTP headers to use in this connection * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks); +GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_strarray *custom_headers); /** * Get the remote repository's reference advertisement list @@ -546,6 +547,11 @@ typedef struct { * The default is to auto-follow tags. */ git_remote_autotag_option_t download_tags; + + /** + * Extra headers for this fetch operation + */ + git_strarray custom_headers; } git_fetch_options; #define GIT_FETCH_OPTIONS_VERSION 1 @@ -585,6 +591,11 @@ typedef struct { * Callbacks to use for this push operation */ git_remote_callbacks callbacks; + + /** + * Extra headers for this push operation + */ + git_strarray custom_headers; } git_push_options; #define GIT_PUSH_OPTIONS_VERSION 1 diff --git a/include/git2/repository.h b/include/git2/repository.h index cf268ef85..85b7e6861 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -675,7 +675,9 @@ typedef enum { GIT_REPOSITORY_STATE_NONE, GIT_REPOSITORY_STATE_MERGE, GIT_REPOSITORY_STATE_REVERT, + GIT_REPOSITORY_STATE_REVERT_SEQUENCE, GIT_REPOSITORY_STATE_CHERRYPICK, + GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE, GIT_REPOSITORY_STATE_BISECT, GIT_REPOSITORY_STATE_REBASE, GIT_REPOSITORY_STATE_REBASE_INTERACTIVE, diff --git a/include/git2/stash.h b/include/git2/stash.h index 526db0ba2..733d75a7f 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -68,7 +68,7 @@ GIT_EXTERN(int) git_stash_save( git_repository *repo, const git_signature *stasher, const char *message, - unsigned int flags); + uint32_t flags); /** Stash application flags. */ typedef enum { @@ -150,7 +150,7 @@ typedef struct git_stash_apply_options { * `GIT_STASH_APPLY_OPTIONS_INIT` here. * @return Zero on success; -1 on failure. */ -int git_stash_apply_init_options( +GIT_EXTERN(int) git_stash_apply_init_options( git_stash_apply_options *opts, unsigned int version); /** diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 689fe4b64..bc94eacaa 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -107,6 +107,17 @@ typedef enum { GIT_SUBMODULE_STATUS_WD_WD_MODIFIED | \ GIT_SUBMODULE_STATUS_WD_UNTRACKED)) != 0) +/** + * Function pointer to receive each submodule + * + * @param sm git_submodule currently being visited + * @param name name of the submodule + * @param payload value you passed to the foreach function as payload + * @return 0 on success or error code + */ +typedef int (*git_submodule_cb)( + git_submodule *sm, const char *name, void *payload); + /** * Submodule update options structure * @@ -239,7 +250,7 @@ GIT_EXTERN(void) git_submodule_free(git_submodule *submodule); */ GIT_EXTERN(int) git_submodule_foreach( git_repository *repo, - int (*callback)(git_submodule *sm, const char *name, void *payload), + git_submodule_cb callback, void *payload); /** diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 044e34417..4dad6da42 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -67,6 +67,20 @@ struct git_config_backend { int (*iterator)(git_config_iterator **, struct git_config_backend *); /** Produce a read-only version of this backend */ int (*snapshot)(struct git_config_backend **, struct git_config_backend *); + /** + * Lock this backend. + * + * Prevent any writes to the data store backing this + * backend. Any updates must not be visible to any other + * readers. + */ + int (*lock)(struct git_config_backend *); + /** + * Unlock the data store backing this backend. If success is + * true, the changes should be committed, otherwise rolled + * back. + */ + int (*unlock)(struct git_config_backend *, int success); void (*free)(struct git_config_backend *); }; #define GIT_CONFIG_BACKEND_VERSION 1 diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h index 5fd8d5566..d0e5d4d6f 100644 --- a/include/git2/sys/filter.h +++ b/include/git2/sys/filter.h @@ -127,17 +127,6 @@ GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *sr */ GIT_EXTERN(uint32_t) git_filter_source_flags(const git_filter_source *src); -/* - * struct git_filter - * - * The filter lifecycle: - * - initialize - first use of filter - * - shutdown - filter removed/unregistered from system - * - check - considering filter for file - * - apply - apply filter to file contents - * - cleanup - done with file - */ - /** * Initialize callback on filter * @@ -233,28 +222,51 @@ typedef void (*git_filter_cleanup_fn)( * To associate extra data with a filter, allocate extra data and put the * `git_filter` struct at the start of your data buffer, then cast the * `self` pointer to your larger structure when your callback is invoked. - * - * `version` should be set to GIT_FILTER_VERSION - * - * `attributes` is a whitespace-separated list of attribute names to check - * for this filter (e.g. "eol crlf text"). If the attribute name is bare, - * it will be simply loaded and passed to the `check` callback. If it has - * a value (i.e. "name=value"), the attribute must match that value for - * the filter to be applied. - * - * The `initialize`, `shutdown`, `check`, `apply`, and `cleanup` callbacks - * are all documented above with the respective function pointer typedefs. */ struct git_filter { + /** The `version` field should be set to `GIT_FILTER_VERSION`. */ unsigned int version; + /** + * A whitespace-separated list of attribute names to check for this + * filter (e.g. "eol crlf text"). If the attribute name is bare, it + * will be simply loaded and passed to the `check` callback. If it + * has a value (i.e. "name=value"), the attribute must match that + * value for the filter to be applied. The value may be a wildcard + * (eg, "name=*"), in which case the filter will be invoked for any + * value for the given attribute name. See the attribute parameter + * of the `check` callback for the attribute value that was specified. + */ const char *attributes; + /** Called when the filter is first used for any file. */ git_filter_init_fn initialize; + + /** Called when the filter is removed or unregistered from the system. */ git_filter_shutdown_fn shutdown; + + /** + * Called to determine whether the filter should be invoked for a + * given file. If this function returns `GIT_PASSTHROUGH` then the + * `apply` function will not be invoked and the contents will be passed + * through unmodified. + */ git_filter_check_fn check; + + /** + * Called to actually apply the filter to file contents. If this + * function returns `GIT_PASSTHROUGH` then the contents will be passed + * through unmodified. + */ git_filter_apply_fn apply; + + /** + * Called to apply the filter in a streaming manner. If this is not + * specified then the system will call `apply` with the whole buffer. + */ git_filter_stream_fn stream; + + /** Called when the system is done filtering for a file. */ git_filter_cleanup_fn cleanup; }; diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h index 29a99f798..2e2b87e68 100644 --- a/include/git2/sys/index.h +++ b/include/git2/sys/index.h @@ -25,7 +25,7 @@ typedef struct git_index_name_entry { /** Representation of a resolve undo entry in the index. */ typedef struct git_index_reuc_entry { - unsigned int mode[3]; + uint32_t mode[3]; git_oid oid[3]; char *path; } git_index_reuc_entry; diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index fe102ff3c..e423a9236 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -83,6 +83,10 @@ struct git_odb_backend { git_odb_writepack **, git_odb_backend *, git_odb *odb, git_transfer_progress_cb progress_cb, void *progress_payload); + /** + * Frees any resources held by the odb (including the `git_odb_backend` + * itself). An odb backend implementation must provide this function. + */ void (* free)(git_odb_backend *); }; diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 8b004a7e0..5129ad84a 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -103,8 +103,9 @@ struct git_refdb_backend { const git_signature *who, const char *message); /** - * Deletes the given reference from the refdb. A refdb implementation - * must provide this function. + * Deletes the given reference (and if necessary its reflog) + * from the refdb. A refdb implementation must provide this + * function. */ int (*del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target); @@ -129,8 +130,8 @@ struct git_refdb_backend { int (*ensure_log)(git_refdb_backend *backend, const char *refname); /** - * Frees any resources held by the refdb. A refdb implementation may - * provide this function; if it is not provided, nothing will be done. + * Frees any resources held by the refdb (including the `git_refdb_backend` + * itself). A refdb backend implementation must provide this function. */ void (*free)(git_refdb_backend *backend); diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 55a714bbb..2b4ff7fd8 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -39,6 +39,19 @@ typedef struct git_stream { void (*free)(struct git_stream *); } git_stream; +typedef int (*git_stream_cb)(git_stream **out, const char *host, const char *port); + +/** + * Register a TLS stream constructor for the library to use + * + * If a constructor is already set, it will be overwritten. Pass + * `NULL` in order to deregister the current constructor. + * + * @param ctor the constructor to use + * @return 0 or an error code + */ +GIT_EXTERN(int) git_stream_register_tls(git_stream_cb ctor); + GIT_END_DECL #endif diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 867fbcbce..ce0234a18 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -10,6 +10,7 @@ #include "git2/net.h" #include "git2/types.h" +#include "git2/strarray.h" /** * @file git2/sys/transport.h @@ -40,6 +41,11 @@ struct git_transport { git_transport_certificate_check_cb certificate_check_cb, void *payload); + /* Set custom headers for HTTP requests */ + int (*set_custom_headers)( + git_transport *transport, + const git_strarray *custom_headers); + /* Connect the transport to the remote repository, using the given * direction. */ int (*connect)( @@ -211,6 +217,28 @@ GIT_EXTERN(int) git_transport_smart( git_remote *owner, /* (git_smart_subtransport_definition *) */ void *payload); +/** + * Call the certificate check for this transport. + * + * @param transport a smart transport + * @param cert the certificate to pass to the caller + * @param valid whether we believe the certificate is valid + * @param hostname the hostname we connected to + * @return the return value of the callback + */ +GIT_EXTERN(int) git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname); + +/** + * Call the credentials callback for this transport + * + * @param out the pointer where the creds are to be stored + * @param transport a smart transport + * @param user the user we saw on the url (if any) + * @param methods available methods for authentication + * @return the return value of the callback + */ +GIT_EXTERN(int) git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods); + /* *** End of base transport interface *** *** Begin interface for subtransports for the smart transport *** diff --git a/include/git2/transport.h b/include/git2/transport.h index 2eeebd565..0ec241699 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -37,39 +37,32 @@ typedef enum { * Hostkey information taken from libssh2 */ typedef struct { + git_cert parent; + /** - * Type of certificate. Here to share the header with - * `git_cert`. + * A hostkey type from libssh2, either + * `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1` */ - git_cert_t cert_type; - /** - * A hostkey type from libssh2, either - * `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1` - */ git_cert_ssh_t type; - /** - * Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will - * have the MD5 hash of the hostkey. - */ + /** + * Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will + * have the MD5 hash of the hostkey. + */ unsigned char hash_md5[16]; - /** - * Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will - * have the SHA-1 hash of the hostkey. - */ - unsigned char hash_sha1[20]; + /** + * Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will + * have the SHA-1 hash of the hostkey. + */ + unsigned char hash_sha1[20]; } git_cert_hostkey; /** * X.509 certificate information */ typedef struct { - /** - * Type of certificate. Here to share the header with - * `git_cert`. - */ - git_cert_t cert_type; + git_cert parent; /** * Pointer to the X.509 certificate data */ @@ -314,6 +307,17 @@ GIT_EXTERN(int) git_cred_ssh_key_memory_new( const char *privatekey, const char *passphrase); + +/** + * Free a credential. + * + * This is only necessary if you own the object; that is, if you are a + * transport. + * + * @param cred the object to free + */ +GIT_EXTERN(void) git_cred_free(git_cred *cred); + /** * Signature of a function which acquires a credential object. * diff --git a/include/git2/version.h b/include/git2/version.h index 3a091e788..66a6623cd 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.23.1" +#define LIBGIT2_VERSION "0.24.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 23 -#define LIBGIT2_VER_REVISION 1 +#define LIBGIT2_VER_MINOR 24 +#define LIBGIT2_VER_REVISION 0 #define LIBGIT2_VER_PATCH 0 -#define LIBGIT2_SOVERSION 23 +#define LIBGIT2_SOVERSION 24 #endif diff --git a/libgit2.pc.in b/libgit2.pc.in index 3d825a49f..329a560a7 100644 --- a/libgit2.pc.in +++ b/libgit2.pc.in @@ -1,11 +1,12 @@ -libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@ -includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@ +prefix=@PKGCONFIG_PREFIX@ +libdir=@PKGCONFIG_LIBDIR@ +includedir=@PKGCONFIG_INCLUDEDIR@ Name: libgit2 Description: The git library, take 2 Version: @LIBGIT2_VERSION_STRING@ -Libs: -L${libdir} -lgit2 +Libs: -L"${libdir}" -lgit2 Libs.private: @LIBGIT2_PC_LIBS@ Requires.private: @LIBGIT2_PC_REQUIRES@ diff --git a/script/cibuild.sh b/script/cibuild.sh index de5df9ea8..00cde0ada 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -25,7 +25,7 @@ git daemon --listen=localhost --export-all --enable=receive-pack --base-path="$H export GITTEST_REMOTE_URL="git://localhost/test.git" # Run the test suite -ctest -V . || exit $? +ctest -V -R libgit2_clar || exit $? # Now that we've tested the raw git protocol, let's set up ssh to we # can do the push tests over it @@ -56,3 +56,7 @@ if [ -e ./libgit2_clar ]; then ./libgit2_clar -sonline::clone::cred_callback || exit $? fi fi + +export GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" +export GITTEST_REMOTE_USER="libgit2test" +ctest -V -R libgit2_clar-cred_callback diff --git a/script/coverity.sh b/script/coverity.sh index dcfeffc1d..8c826892f 100755 --- a/script/coverity.sh +++ b/script/coverity.sh @@ -33,6 +33,8 @@ if [ ! -d "$TOOL_BASE" ]; then ln -s "$TOOL_DIR" "$TOOL_BASE"/cov-analysis fi +cp script/user_nodefs.h "$TOOL_BASE"/cov-analysis/config/user_nodefs.h + COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build" # Configure and build @@ -48,10 +50,9 @@ COVERITY_UNSUPPORTED=1 \ tar czf libgit2.tgz cov-int SHA=$(git rev-parse --short HEAD) curl \ - --form project=libgit2 \ --form token="$COVERITY_TOKEN" \ --form email=bs@github.com \ --form file=@libgit2.tgz \ --form version="$SHA" \ --form description="Travis build" \ - http://scan5.coverity.com/cgi-bin/upload.py + https://scan.coverity.com/builds?project=libgit2 diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh index c2e0162d8..5510379d4 100755 --- a/script/install-deps-osx.sh +++ b/script/install-deps-osx.sh @@ -2,4 +2,5 @@ set -x -brew install libssh2 cmake +brew update +brew install libssh2 diff --git a/script/user_nodefs.h b/script/user_nodefs.h new file mode 100644 index 000000000..3c06a706d --- /dev/null +++ b/script/user_nodefs.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#nodef GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { __coverity_panic__(); } +#nodef GITERR_CHECK_ALLOC_BUF(buf) if (buf == NULL || git_buf_oom(buf)) { __coverity_panic__(); } + +#nodef GITERR_CHECK_ALLOC_ADD(out, one, two) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { __coverity_panic__(); } + +#nodef GITERR_CHECK_ALLOC_ADD3(out, one, two, three) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { __coverity_panic__(); } + +#nodef GITERR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { __coverity_panic__(); } + +#nodef GITERR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ + if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { __coverity_panic__(); } + +#nodef GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) { __coverity_panic__(); } + +#nodef LOOKS_LIKE_DRIVE_PREFIX(S) (strlen(S) >= 2 && git__isalpha((S)[0]) && (S)[1] == ':') + +#nodef git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (v)->contents != NULL && (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#nodef git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length - 1; (v)->contents != NULL && (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) diff --git a/src/annotated_commit.c b/src/annotated_commit.c index 3f2d2ed17..e53b95dee 100644 --- a/src/annotated_commit.c +++ b/src/annotated_commit.c @@ -7,12 +7,16 @@ #include "common.h" #include "annotated_commit.h" +#include "refs.h" +#include "cache.h" #include "git2/commit.h" #include "git2/refs.h" #include "git2/repository.h" #include "git2/annotated_commit.h" #include "git2/revparse.h" +#include "git2/tree.h" +#include "git2/index.h" static int annotated_commit_init( git_annotated_commit **out, @@ -22,14 +26,17 @@ static int annotated_commit_init( const char *remote_url) { git_annotated_commit *annotated_commit; + git_commit *commit = NULL; int error = 0; assert(out && id); *out = NULL; - annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); - GITERR_CHECK_ALLOC(annotated_commit); + if ((error = git_commit_lookup(&commit, repo, id)) < 0 || + (error = git_annotated_commit_from_commit(&annotated_commit, + commit)) < 0) + goto done; if (ref_name) { annotated_commit->ref_name = git__strdup(ref_name); @@ -41,15 +48,10 @@ static int annotated_commit_init( GITERR_CHECK_ALLOC(annotated_commit->remote_url); } - git_oid_fmt(annotated_commit->id_str, id); - annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; - - if ((error = git_commit_lookup(&annotated_commit->commit, repo, id)) < 0) { - git_annotated_commit_free(annotated_commit); - return error; - } - *out = annotated_commit; + +done: + git_commit_free(commit); return error; } @@ -75,6 +77,51 @@ int git_annotated_commit_from_ref( return error; } +int git_annotated_commit_from_head( + git_annotated_commit **out, + git_repository *repo) +{ + git_reference *head; + int error; + + assert(out && repo); + + *out = NULL; + + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return -1; + + error = git_annotated_commit_from_ref(out, repo, head); + + git_reference_free(head); + return error; +} + +int git_annotated_commit_from_commit( + git_annotated_commit **out, + git_commit *commit) +{ + git_annotated_commit *annotated_commit; + + assert(out && commit); + + *out = NULL; + + annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); + GITERR_CHECK_ALLOC(annotated_commit); + + annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; + + git_cached_obj_incref(commit); + annotated_commit->commit = commit; + + git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); + annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; + + *out = annotated_commit; + return 0; +} + int git_annotated_commit_lookup( git_annotated_commit **out, git_repository *repo, @@ -136,14 +183,20 @@ void git_annotated_commit_free(git_annotated_commit *annotated_commit) if (annotated_commit == NULL) return; - if (annotated_commit->commit != NULL) - git_commit_free(annotated_commit->commit); - - if (annotated_commit->ref_name != NULL) - git__free(annotated_commit->ref_name); - - if (annotated_commit->remote_url != NULL) - git__free(annotated_commit->remote_url); + switch (annotated_commit->type) { + case GIT_ANNOTATED_COMMIT_REAL: + git_commit_free(annotated_commit->commit); + git_tree_free(annotated_commit->tree); + git__free(annotated_commit->ref_name); + git__free(annotated_commit->remote_url); + break; + case GIT_ANNOTATED_COMMIT_VIRTUAL: + git_index_free(annotated_commit->index); + git_array_clear(annotated_commit->parents); + break; + default: + abort(); + } git__free(annotated_commit); } diff --git a/src/annotated_commit.h b/src/annotated_commit.h index e873184ae..cbb88fd22 100644 --- a/src/annotated_commit.h +++ b/src/annotated_commit.h @@ -7,11 +7,31 @@ #ifndef INCLUDE_annotated_commit_h__ #define INCLUDE_annotated_commit_h__ +#include "oidarray.h" + #include "git2/oid.h" -/** Internal structure for merge inputs */ +typedef enum { + GIT_ANNOTATED_COMMIT_REAL = 1, + GIT_ANNOTATED_COMMIT_VIRTUAL = 2, +} git_annotated_commit_t; + +/** + * Internal structure for merge inputs. An annotated commit is generally + * "real" and backed by an actual commit in the repository, but merge will + * internally create "virtual" commits that are in-memory intermediate + * commits backed by an index. + */ struct git_annotated_commit { + git_annotated_commit_t type; + + /* real commit */ git_commit *commit; + git_tree *tree; + + /* virtual commit structure */ + git_index *index; + git_array_oid_t parents; char *ref_name; char *remote_url; @@ -19,4 +39,9 @@ struct git_annotated_commit { char id_str[GIT_OID_HEXSZ+1]; }; +extern int git_annotated_commit_from_head(git_annotated_commit **out, + git_repository *repo); +extern int git_annotated_commit_from_commit(git_annotated_commit **out, + git_commit *commit); + #endif diff --git a/src/attr_file.c b/src/attr_file.c index 89706865a..11d149358 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -35,11 +35,7 @@ int git_attr_file__new( return -1; } - if (git_pool_init(&attrs->pool, 1, 0) < 0) { - attr_file_free(attrs); - return -1; - } - + git_pool_init(&attrs->pool, 1); GIT_REFCOUNT_INC(attrs); attrs->entry = entry; attrs->source = source; @@ -127,7 +123,7 @@ int git_attr_file__load( break; } case GIT_ATTR_FILE__FROM_FILE: { - int fd; + int fd = -1; /* For open or read errors, pretend that we got ENOTFOUND. */ /* TODO: issue warning when warning API is available */ @@ -137,7 +133,8 @@ int git_attr_file__load( (fd = git_futils_open_ro(entry->fullpath)) < 0 || (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) nonexistent = true; - else + + if (fd >= 0) p_close(fd); break; diff --git a/src/attrcache.c b/src/attrcache.c index 5bc260460..a57110684 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -388,10 +388,11 @@ int git_attr_cache__do_init(git_repository *repo) * hashtable for attribute macros, and string pool */ if ((ret = git_strmap_alloc(&cache->files)) < 0 || - (ret = git_strmap_alloc(&cache->macros)) < 0 || - (ret = git_pool_init(&cache->pool, 1, 0)) < 0) + (ret = git_strmap_alloc(&cache->macros)) < 0) goto cancel; + git_pool_init(&cache->pool, 1); + cache = git__compare_and_swap(&repo->attrcache, NULL, cache); if (cache) goto cancel; /* raced with another thread, free this but no error */ diff --git a/src/blame.c b/src/blame.c index a52f9402b..2daf91591 100644 --- a/src/blame.c +++ b/src/blame.c @@ -23,8 +23,8 @@ static int hunk_byfinalline_search_cmp(const void *key, const void *entry) git_blame_hunk *hunk = (git_blame_hunk*)entry; size_t lineno = *(size_t*)key; - size_t lines_in_hunk = (size_t)hunk->lines_in_hunk; - size_t final_start_line_number = (size_t)hunk->final_start_line_number; + size_t lines_in_hunk = hunk->lines_in_hunk; + size_t final_start_line_number = hunk->final_start_line_number; if (lineno < final_start_line_number) return -1; @@ -44,7 +44,7 @@ static int hunk_cmp(const void *_a, const void *_b) static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line) { - return line >= (size_t)(hunk->final_start_line_number + hunk->lines_in_hunk - 1); + return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1); } static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) @@ -53,9 +53,9 @@ static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) } static git_blame_hunk* new_hunk( - uint16_t start, - uint16_t lines, - uint16_t orig_start, + size_t start, + size_t lines, + size_t orig_start, const char *path) { git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); @@ -166,9 +166,9 @@ const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t inde return (git_blame_hunk*)git_vector_get(&blame->hunks, index); } -const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno) +const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) { - size_t i, new_lineno = (size_t)lineno; + size_t i, new_lineno = lineno; assert(blame); if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { @@ -223,8 +223,8 @@ static git_blame_hunk *split_hunk_in_vector( } new_line_count = hunk->lines_in_hunk - rel_line; - nh = new_hunk((uint16_t)(hunk->final_start_line_number+rel_line), (uint16_t)new_line_count, - (uint16_t)(hunk->orig_start_line_number+rel_line), hunk->orig_path); + nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count, + hunk->orig_start_line_number + rel_line, hunk->orig_path); if (!nh) return NULL; @@ -233,7 +233,7 @@ static git_blame_hunk *split_hunk_in_vector( git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id); /* Adjust hunk that was split */ - hunk->lines_in_hunk -= (uint16_t)new_line_count; + hunk->lines_in_hunk -= new_line_count; git_vector_insert_sorted(vec, nh, NULL); { git_blame_hunk *ret = return_new ? nh : hunk; @@ -331,7 +331,7 @@ static int blame_internal(git_blame *blame) blame->ent = ent; - git_blame__like_git(blame, blame->options.flags); + error = git_blame__like_git(blame, blame->options.flags); cleanup: for (ent = blame->ent; ent; ) { @@ -442,7 +442,7 @@ static int buffer_line_cb( } else { /* Create a new buffer-blame hunk with this line */ shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); - blame->current_hunk = new_hunk((uint16_t)blame->current_diff_line, 1, 0, blame->path); + blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path); GITERR_CHECK_ALLOC(blame->current_hunk); git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); diff --git a/src/blame.h b/src/blame.h index 7e23de808..d8db8d5c1 100644 --- a/src/blame.h +++ b/src/blame.h @@ -31,10 +31,10 @@ typedef struct git_blame__entry { /* the first line of this group in the final image; * internally all line numbers are 0 based. */ - int lno; + size_t lno; /* how many lines this group has */ - int num_lines; + size_t num_lines; /* the commit that introduced this group into the final image */ git_blame__origin *suspect; @@ -51,7 +51,7 @@ typedef struct git_blame__entry { /* the line number of the first line of this group in the * suspect's file; internally all line numbers are 0 based. */ - int s_lno; + size_t s_lno; /* how significant this entry is -- cached to avoid * scanning the lines over and over. diff --git a/src/blame_git.c b/src/blame_git.c index f481426aa..b8b568285 100644 --- a/src/blame_git.c +++ b/src/blame_git.c @@ -9,6 +9,7 @@ #include "commit.h" #include "blob.h" #include "xdiff/xinclude.h" +#include "diff_xdiff.h" /* * Origin is refcounted and usually we keep the blob contents to be @@ -92,18 +93,25 @@ static bool same_suspect(git_blame__origin *a, git_blame__origin *b) } /* find the line number of the last line the target is suspected for */ -static int find_last_in_target(git_blame *blame, git_blame__origin *target) +static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target) { git_blame__entry *e; - int last_in_target = -1; + size_t last_in_target = 0; + bool found = false; + + *out = 0; for (e=blame->ent; e; e=e->next) { if (e->guilty || !same_suspect(e->suspect, target)) continue; - if (last_in_target < e->s_lno + e->num_lines) + if (last_in_target < e->s_lno + e->num_lines) { + found = true; last_in_target = e->s_lno + e->num_lines; + } } - return last_in_target; + + *out = last_in_target; + return found; } /* @@ -121,9 +129,9 @@ static int find_last_in_target(git_blame *blame, git_blame__origin *target) * to be blamed for the parent, and after that portion. */ static void split_overlap(git_blame__entry *split, git_blame__entry *e, - int tlno, int plno, int same, git_blame__origin *parent) + size_t tlno, size_t plno, size_t same, git_blame__origin *parent) { - int chunk_end_lno; + size_t chunk_end_lno; if (e->s_lno < tlno) { /* there is a pre-chunk part not blamed on the parent */ @@ -264,9 +272,9 @@ static void decref_split(git_blame__entry *split) static void blame_overlap( git_blame *blame, git_blame__entry *e, - int tlno, - int plno, - int same, + size_t tlno, + size_t plno, + size_t same, git_blame__origin *parent) { git_blame__entry split[3] = {{0}}; @@ -284,9 +292,9 @@ static void blame_overlap( */ static void blame_chunk( git_blame *blame, - int tlno, - int plno, - int same, + size_t tlno, + size_t plno, + size_t same, git_blame__origin *target, git_blame__origin *parent) { @@ -313,7 +321,7 @@ static int my_emit( blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent); d->plno = start_a + count_a; d->tlno = start_b + count_b; - + return 0; } @@ -351,6 +359,13 @@ static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data) ecb.priv = cb_data; trim_common_tail(&file_a, &file_b, 0); + + if (file_a.size > GIT_XDIFF_MAX_SIZE || + file_b.size > GIT_XDIFF_MAX_SIZE) { + giterr_set(GITERR_INVALID, "file too large to blame"); + return -1; + } + return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); } @@ -368,18 +383,19 @@ static int pass_blame_to_parent( git_blame__origin *target, git_blame__origin *parent) { - int last_in_target; + size_t last_in_target; mmfile_t file_p, file_o; blame_chunk_cb_data d = { blame, target, parent, 0, 0 }; - last_in_target = find_last_in_target(blame, target); - if (last_in_target < 0) + if (!find_last_in_target(&last_in_target, blame, target)) return 1; /* nothing remains for this target */ fill_origin_blob(parent, &file_p); fill_origin_blob(target, &file_o); - diff_hunks(file_p, file_o, &d); + if (diff_hunks(file_p, file_o, &d) < 0) + return -1; + /* The reset (i.e. anything after tlno) are the same as the parent */ blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent); @@ -477,12 +493,13 @@ static void pass_whole_blame(git_blame *blame, } } -static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) +static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) { git_commit *commit = origin->commit; int i, num_parents; git_blame__origin *sg_buf[16]; git_blame__origin *porigin, **sg_origin = sg_buf; + int ret, error = 0; num_parents = git_commit_parentcount(commit); if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) @@ -540,8 +557,13 @@ static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt origin_incref(porigin); origin->previous = porigin; } - if (pass_blame_to_parent(blame, origin, porigin)) + + if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) { + if (ret < 0) + error = -1; + goto finish; + } } /* TODO: optionally find moves in parents' files */ @@ -554,7 +576,7 @@ finish: origin_decref(sg_origin[i]); if (sg_origin != sg_buf) git__free(sg_origin); - return; + return error; } /* @@ -583,7 +605,7 @@ static void coalesce(git_blame *blame) } } -void git_blame__like_git(git_blame *blame, uint32_t opt) +int git_blame__like_git(git_blame *blame, uint32_t opt) { while (true) { git_blame__entry *ent; @@ -594,11 +616,13 @@ void git_blame__like_git(git_blame *blame, uint32_t opt) if (!ent->guilty) suspect = ent->suspect; if (!suspect) - return; /* all done */ + return 0; /* all done */ /* We'll use this suspect later in the loop, so hold on to it for now. */ origin_incref(suspect); - pass_blame(blame, suspect, opt); + + if (pass_blame(blame, suspect, opt) < 0) + return -1; /* Take responsibility for the remaining entries */ for (ent = blame->ent; ent; ent = ent->next) { @@ -613,6 +637,8 @@ void git_blame__like_git(git_blame *blame, uint32_t opt) } coalesce(blame); + + return 0; } void git_blame__free_entry(git_blame__entry *ent) diff --git a/src/blame_git.h b/src/blame_git.h index 3ec2710b8..1891b0e1f 100644 --- a/src/blame_git.h +++ b/src/blame_git.h @@ -15,6 +15,6 @@ int git_blame__get_origin( git_commit *commit, const char *path); void git_blame__free_entry(git_blame__entry *ent); -void git_blame__like_git(git_blame *sb, uint32_t flags); +int git_blame__like_git(git_blame *sb, uint32_t flags); #endif diff --git a/src/branch.c b/src/branch.c index 791d55106..0dcc14c29 100644 --- a/src/branch.c +++ b/src/branch.c @@ -155,18 +155,7 @@ int git_branch_delete(git_reference *branch) git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0) goto on_error; - if (git_reference_delete(branch) < 0) - goto on_error; - - if ((error = git_reflog_delete(git_reference_owner(branch), git_reference_name(branch))) < 0) { - if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = 0; - } - goto on_error; - } - - error = 0; + error = git_reference_delete(branch); on_error: git_buf_free(&config_section); diff --git a/src/checkout.c b/src/checkout.c index 4b3acbcce..deeee62e0 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -18,6 +18,7 @@ #include "git2/submodule.h" #include "git2/sys/index.h" #include "git2/sys/filter.h" +#include "git2/merge.h" #include "refs.h" #include "repository.h" @@ -27,7 +28,7 @@ #include "diff.h" #include "pathspec.h" #include "buf_text.h" -#include "merge_file.h" +#include "diff_xdiff.h" #include "path.h" #include "attr.h" #include "pool.h" @@ -199,8 +200,7 @@ static bool checkout_is_workdir_modified( * out.) */ if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) { - if (wditem->mtime.seconds == ie->mtime.seconds && - wditem->mtime.nanoseconds == ie->mtime.nanoseconds && + 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); } @@ -243,6 +243,12 @@ static int checkout_action_common( if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) *action |= CHECKOUT_ACTION__REMOVE; + /* if the file is on disk and doesn't match our mode, force update */ + if (wd && + GIT_PERMS_IS_EXEC(wd->mode) != + GIT_PERMS_IS_EXEC(delta->new_file.mode)) + *action |= CHECKOUT_ACTION__REMOVE; + notify = GIT_CHECKOUT_NOTIFY_UPDATED; } @@ -1220,7 +1226,7 @@ static int checkout_verify_paths( int action, git_diff_delta *delta) { - unsigned int flags = GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT; + unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; if (action & CHECKOUT_ACTION__REMOVE) { if (!git_path_isvalid(repo, delta->old_file.path, flags)) { @@ -1248,11 +1254,13 @@ static int checkout_get_actions( int error = 0, act; const git_index_entry *wditem; git_vector pathspec = GIT_VECTOR_INIT, *deltas; - git_pool pathpool = GIT_POOL_INIT_STRINGPOOL; + git_pool pathpool; git_diff_delta *delta; size_t i, *counts = NULL; uint32_t *actions = NULL; + git_pool_init(&pathpool, 1); + if (data->opts.paths.count > 0 && git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; @@ -1360,7 +1368,7 @@ static int checkout_mkdir( mkdir_opts.dir_map = data->mkdir_map; mkdir_opts.pool = &data->pool; - error = git_futils_mkdir_ext( + error = git_futils_mkdir_relative( path, base, mode, flags, &mkdir_opts); data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; @@ -1479,8 +1487,10 @@ static int blob_content_to_file( if (!data->opts.disable_filters && (error = git_filter_list__load_ext( &fl, data->repo, blob, hint_path, - GIT_FILTER_TO_WORKTREE, &filter_opts))) + GIT_FILTER_TO_WORKTREE, &filter_opts))) { + p_close(fd); return error; + } /* setup the writer */ memset(&writer, 0, sizeof(struct checkout_stream)); @@ -1500,15 +1510,6 @@ static int blob_content_to_file( if (error < 0) return error; - if (GIT_PERMS_IS_EXEC(mode)) { - data->perfdata.chmod_calls++; - - if ((error = p_chmod(path, mode)) < 0) { - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); - return error; - } - } - if (st) { data->perfdata.stat_calls++; @@ -2441,10 +2442,11 @@ static int checkout_data_init( git_config_entry_free(conflict_style); } + git_pool_init(&data->pool, 1); + if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || - (error = git_pool_init(&data->pool, 1, 0)) < 0 || (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || (error = git_path_to_dir(&data->path)) < 0 || (error = git_strmap_alloc(&data->mkdir_map)) < 0) @@ -2471,11 +2473,12 @@ int git_checkout_iterator( { int error = 0; git_iterator *baseline = NULL, *workdir = NULL; + git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, + workdir_opts = GIT_ITERATOR_OPTIONS_INIT; checkout_data data = {0}; git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; uint32_t *actions = NULL; size_t *counts = NULL; - git_iterator_flag_t iterflags = 0; /* initialize structures and options */ error = checkout_data_init(&data, target, opts); @@ -2499,25 +2502,31 @@ int git_checkout_iterator( /* set up iterators */ - iterflags = git_iterator_ignore_case(target) ? + workdir_opts.flags = git_iterator_ignore_case(target) ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; + workdir_opts.start = data.pfx; + workdir_opts.end = data.pfx; if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_workdir_ext( &workdir, data.repo, data.opts.target_directory, index, NULL, - iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, - data.pfx, data.pfx)) < 0) + &workdir_opts)) < 0) goto cleanup; + baseline_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + baseline_opts.start = data.pfx; + baseline_opts.end = data.pfx; + if (data.opts.baseline_index) { if ((error = git_iterator_for_index( - &baseline, data.opts.baseline_index, - iterflags, data.pfx, data.pfx)) < 0) + &baseline, git_index_owner(data.opts.baseline_index), + data.opts.baseline_index, &baseline_opts)) < 0) goto cleanup; } else { if ((error = git_iterator_for_tree( - &baseline, data.opts.baseline, - iterflags, data.pfx, data.pfx)) < 0) + &baseline, data.opts.baseline, &baseline_opts)) < 0) goto cleanup; } @@ -2625,7 +2634,7 @@ int git_checkout_index( return error; GIT_REFCOUNT_INC(index); - if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL))) + if (!(error = git_iterator_for_index(&index_i, repo, index, NULL))) error = git_checkout_iterator(index_i, index, opts); if (owned) @@ -2646,6 +2655,7 @@ int git_checkout_tree( git_index *index; git_tree *tree = NULL; git_iterator *tree_i = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; if (!treeish && !repo) { giterr_set(GITERR_CHECKOUT, @@ -2681,7 +2691,12 @@ int git_checkout_tree( if ((error = git_repository_index(&index, repo)) < 0) return error; - if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) + if ((opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { + iter_opts.pathlist.count = opts->paths.count; + iter_opts.pathlist.strings = opts->paths.strings; + } + + if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) error = git_checkout_iterator(tree_i, index, opts); git_iterator_free(tree_i); diff --git a/src/clone.c b/src/clone.c index 070daf94d..6b4b7ae53 100644 --- a/src/clone.c +++ b/src/clone.c @@ -440,14 +440,14 @@ int git_clone( if (error != 0) { git_error_state last_error = {0}; - giterr_capture(&last_error, error); + giterr_state_capture(&last_error, error); git_repository_free(repo); repo = NULL; (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); - giterr_restore(&last_error); + giterr_state_restore(&last_error); } *out = repo; diff --git a/src/commit.c b/src/commit.c index 616f947db..685c642aa 100644 --- a/src/commit.c +++ b/src/commit.c @@ -17,6 +17,7 @@ #include "signature.h" #include "message.h" #include "refs.h" +#include "object.h" void git_commit__free(void *_commit) { @@ -31,11 +32,12 @@ void git_commit__free(void *_commit) git__free(commit->raw_message); git__free(commit->message_encoding); git__free(commit->summary); + git__free(commit->body); git__free(commit); } -int git_commit_create_from_callback( +static int git_commit__create_internal( git_oid *id, git_repository *repo, const char *update_ref, @@ -45,7 +47,8 @@ int git_commit_create_from_callback( const char *message, const git_oid *tree, git_commit_parent_callback parent_cb, - void *parent_payload) + void *parent_payload, + bool validate) { git_reference *ref = NULL; int error = 0, matched_parent = 0; @@ -57,6 +60,9 @@ int git_commit_create_from_callback( assert(id && repo && tree && parent_cb); + if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE)) + return -1; + if (update_ref) { error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); if (error < 0 && error != GIT_ENOTFOUND) @@ -70,6 +76,11 @@ int git_commit_create_from_callback( git_oid__writebuf(&commit, "tree ", tree); while ((parent = parent_cb(i, parent_payload)) != NULL) { + if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) { + error = -1; + goto on_error; + } + git_oid__writebuf(&commit, "parent ", parent); if (i == 0 && current_id && git_oid_equal(current_id, parent)) matched_parent = 1; @@ -113,10 +124,26 @@ int git_commit_create_from_callback( on_error: git_buf_free(&commit); - giterr_set(GITERR_OBJECT, "Failed to create commit."); return -1; } +int git_commit_create_from_callback( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_commit_parent_callback parent_cb, + void *parent_payload) +{ + return git_commit__create_internal( + id, repo, update_ref, author, committer, message_encoding, message, + tree, parent_cb, parent_payload, true); +} + typedef struct { size_t total; va_list args; @@ -152,10 +179,10 @@ int git_commit_create_v( data.total = parent_count; va_start(data.args, parent_count); - error = git_commit_create_from_callback( + error = git_commit__create_internal( id, repo, update_ref, author, committer, message_encoding, message, git_tree_id(tree), - commit_parent_from_varargs, &data); + commit_parent_from_varargs, &data, false); va_end(data.args); return error; @@ -186,10 +213,10 @@ int git_commit_create_from_ids( { commit_parent_oids data = { parent_count, parents }; - return git_commit_create_from_callback( + return git_commit__create_internal( id, repo, update_ref, author, committer, message_encoding, message, tree, - commit_parent_from_ids, &data); + commit_parent_from_ids, &data, true); } typedef struct { @@ -226,10 +253,10 @@ int git_commit_create( assert(tree && git_tree_owner(tree) == repo); - return git_commit_create_from_callback( + return git_commit__create_internal( id, repo, update_ref, author, committer, message_encoding, message, git_tree_id(tree), - commit_parent_from_array, &data); + commit_parent_from_array, &data, false); } static const git_oid *commit_parent_for_amend(size_t curr, void *payload) @@ -289,9 +316,9 @@ int git_commit_amend( } } - error = git_commit_create_from_callback( + error = git_commit__create_internal( id, repo, NULL, author, committer, message_encoding, message, - &tree_id, commit_parent_for_amend, (void *)commit_to_amend); + &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false); if (!error && update_ref) { error = git_reference__update_for_commit( @@ -431,22 +458,37 @@ const char *git_commit_summary(git_commit *commit) { git_buf summary = GIT_BUF_INIT; const char *msg, *space; + bool space_contains_newline = false; assert(commit); if (!commit->summary) { for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { - if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) + char next_character = msg[0]; + /* stop processing at the end of the first paragraph */ + if (next_character == '\n' && (!msg[1] || msg[1] == '\n')) break; - else if (msg[0] == '\n') - git_buf_putc(&summary, ' '); - else if (git__isspace(msg[0])) - space = space ? space : msg; - else if (space) { - git_buf_put(&summary, space, (msg - space) + 1); - space = NULL; - } else - git_buf_putc(&summary, *msg); + /* record the beginning of contiguous whitespace runs */ + else if (git__isspace(next_character)) { + if(space == NULL) { + space = msg; + space_contains_newline = false; + } + space_contains_newline |= next_character == '\n'; + } + /* the next character is non-space */ + else { + /* process any recorded whitespace */ + if (space) { + if(space_contains_newline) + git_buf_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ + else + git_buf_put(&summary, space, (msg - space)); /* otherwise copy it */ + space = NULL; + } + /* copy the next character */ + git_buf_putc(&summary, next_character); + } } commit->summary = git_buf_detach(&summary); @@ -457,6 +499,33 @@ const char *git_commit_summary(git_commit *commit) return commit->summary; } +const char *git_commit_body(git_commit *commit) +{ + const char *msg, *end; + + assert(commit); + + if (!commit->body) { + /* search for end of summary */ + for (msg = git_commit_message(commit); *msg; ++msg) + if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) + break; + + /* trim leading and trailing whitespace */ + for (; *msg; ++msg) + if (!git__isspace(*msg)) + break; + for (end = msg + strlen(msg) - 1; msg <= end; --end) + if (!git__isspace(*end)) + break; + + if (*msg) + commit->body = git__strndup(msg, end - msg + 1); + } + + return commit->body; +} + int git_commit_tree(git_tree **tree_out, const git_commit *commit) { assert(commit); @@ -521,17 +590,103 @@ int git_commit_nth_gen_ancestor( int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field) { - const char *buf = commit->raw_header; - const char *h, *eol; + const char *eol, *buf = commit->raw_header; git_buf_sanitize(out); + + while ((eol = strchr(buf, '\n'))) { + /* We can skip continuations here */ + if (buf[0] == ' ') { + buf = eol + 1; + continue; + } + + /* Skip until we find the field we're after */ + if (git__prefixcmp(buf, field)) { + buf = eol + 1; + continue; + } + + buf += strlen(field); + /* Check that we're not matching a prefix but the field itself */ + if (buf[0] != ' ') { + buf = eol + 1; + continue; + } + + buf++; /* skip the SP */ + + git_buf_put(out, buf, eol - buf); + if (git_buf_oom(out)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_buf_putc(out, '\n'); + buf = eol + 2; + eol = strchr(buf, '\n'); + if (!eol) + goto malformed; + + git_buf_put(out, buf, eol - buf); + } + + if (git_buf_oom(out)) + goto oom; + + return 0; + } + + giterr_set(GITERR_OBJECT, "no such field '%s'", field); + return GIT_ENOTFOUND; + +malformed: + giterr_set(GITERR_OBJECT, "malformed header"); + return -1; +oom: + giterr_set_oom(); + return -1; +} + +int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field) +{ + git_odb_object *obj; + git_odb *odb; + const char *buf; + const char *h, *eol; + int error; + + git_buf_sanitize(signature); + git_buf_sanitize(signed_data); + + if (!field) + field = "gpgsig"; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + if ((error = git_odb_read(&obj, odb, commit_id)) < 0) + return error; + + if (obj->cached.type != GIT_OBJ_COMMIT) { + giterr_set(GITERR_INVALID, "the requested type does not match the type in ODB"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + buf = git_odb_object_data(obj); + while ((h = strchr(buf, '\n')) && h[1] != '\0' && h[1] != '\n') { h++; - if (git__prefixcmp(h, field)) { + if (git__prefixcmp(buf, field)) { + if (git_buf_put(signed_data, buf, h - buf) < 0) + return -1; + buf = h; continue; } + h = buf; h += strlen(field); eol = strchr(h, '\n'); if (h[0] != ' ') { @@ -543,33 +698,44 @@ int git_commit_header_field(git_buf *out, const git_commit *commit, const char * h++; /* skip the SP */ - git_buf_put(out, h, eol - h); - if (git_buf_oom(out)) + git_buf_put(signature, h, eol - h); + if (git_buf_oom(signature)) goto oom; /* If the next line starts with SP, it's multi-line, we must continue */ while (eol[1] == ' ') { - git_buf_putc(out, '\n'); + git_buf_putc(signature, '\n'); h = eol + 2; eol = strchr(h, '\n'); if (!eol) goto malformed; - git_buf_put(out, h, eol - h); + git_buf_put(signature, h, eol - h); } - if (git_buf_oom(out)) + if (git_buf_oom(signature)) goto oom; - return 0; + git_odb_object_free(obj); + return git_buf_puts(signed_data, eol+1); } - return GIT_ENOTFOUND; + giterr_set(GITERR_OBJECT, "this commit is not signed"); + error = GIT_ENOTFOUND; + goto cleanup; malformed: giterr_set(GITERR_OBJECT, "malformed header"); - return -1; + error = -1; + goto cleanup; oom: giterr_set_oom(); - return -1; + error = -1; + goto cleanup; + +cleanup: + git_odb_object_free(obj); + git_buf_clear(signature); + git_buf_clear(signed_data); + return error; } diff --git a/src/commit.h b/src/commit.h index efb080b50..d01ac2b2f 100644 --- a/src/commit.h +++ b/src/commit.h @@ -28,6 +28,7 @@ struct git_commit { char *raw_header; char *summary; + char *body; }; void git_commit__free(void *commit); diff --git a/src/commit_list.c b/src/commit_list.c index 3054c18dd..28948c88b 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -47,7 +47,7 @@ git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_ git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk) { - return (git_commit_list_node *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC); + return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1); } static int commit_error(git_commit_list_node *commit, const char *msg) @@ -110,7 +110,7 @@ static int commit_quick_parse( const uint8_t *buffer_end = buffer + buffer_len; const uint8_t *parents_start, *committer_start; int i, parents = 0; - int commit_time; + int64_t commit_time; buffer += strlen("tree ") + GIT_OID_HEXSZ + 1; @@ -166,10 +166,10 @@ static int commit_quick_parse( buffer--; } - if ((buffer == committer_start) || (git__strtol32(&commit_time, (char *)(buffer + 1), NULL, 10) < 0)) + if ((buffer == committer_start) || (git__strtol64(&commit_time, (char *)(buffer + 1), NULL, 10) < 0)) return commit_error(commit, "cannot parse commit time"); - commit->time = (time_t)commit_time; + commit->time = commit_time; commit->parsed = 1; return 0; } diff --git a/src/commit_list.h b/src/commit_list.h index 6b3f473d3..a6967bcef 100644 --- a/src/commit_list.h +++ b/src/commit_list.h @@ -13,6 +13,7 @@ #define PARENT2 (1 << 1) #define RESULT (1 << 2) #define STALE (1 << 3) +#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT) #define PARENTS_PER_COMMIT 2 #define COMMIT_ALLOC \ @@ -22,7 +23,7 @@ typedef struct git_commit_list_node { git_oid oid; - uint32_t time; + int64_t time; unsigned int seen:1, uninteresting:1, topo_delay:1, diff --git a/src/common.h b/src/common.h index cdb0b514d..9abd605cb 100644 --- a/src/common.h +++ b/src/common.h @@ -41,11 +41,16 @@ # include # include "win32/msvc-compat.h" # include "win32/mingw-compat.h" +# include "win32/win32-compat.h" # include "win32/error.h" # include "win32/version.h" # ifdef GIT_THREADS # include "win32/pthread.h" # endif +# if defined(GIT_MSVC_CRTDBG) +# include "win32/w32_stack.h" +# include "win32/w32_crtdbg_stacktrace.h" +# endif #else @@ -57,6 +62,12 @@ # endif #define GIT_STDLIB_CALL +#ifdef GIT_USE_STAT_ATIMESPEC +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +#endif + # include #endif @@ -78,6 +89,11 @@ */ #define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } +/** + * Check a buffer allocation result, returning -1 if it failed. + */ +#define GITERR_CHECK_ALLOC_BUF(buf) if ((void *)(buf) == NULL || git_buf_oom(buf)) { return -1; } + /** * Check a return value and propagate result if non-zero. */ @@ -137,20 +153,25 @@ void giterr_system_set(int code); * Structure to preserve libgit2 error state */ typedef struct { - int error_code; + int error_code; + unsigned int oom : 1; git_error error_msg; } git_error_state; /** * Capture current error state to restore later, returning error code. - * If `error_code` is zero, this does nothing and returns zero. + * If `error_code` is zero, this does not clear the current error state. + * You must either restore this error state, or free it. */ -int giterr_capture(git_error_state *state, int error_code); +extern int giterr_state_capture(git_error_state *state, int error_code); /** * Restore error state to a previous value, returning saved error code. */ -int giterr_restore(git_error_state *state); +extern int giterr_state_restore(git_error_state *state); + +/** Free an error state. */ +extern void giterr_state_free(git_error_state *state); /** * Check a versioned structure for validity @@ -199,6 +220,15 @@ GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int v #define GITERR_CHECK_ALLOC_ADD(out, one, two) \ if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } +#define GITERR_CHECK_ALLOC_ADD3(out, one, two, three) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } + +#define GITERR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { 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; } diff --git a/src/config.c b/src/config.c index 77cf573e6..f4d4cb2b9 100644 --- a/src/config.c +++ b/src/config.c @@ -13,6 +13,7 @@ #include "vector.h" #include "buf_text.h" #include "config_file.h" +#include "transaction.h" #if GIT_WIN32 # include #endif @@ -1085,6 +1086,12 @@ int git_config_find_system(git_buf *path) return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); } +int git_config_find_programdata(git_buf *path) +{ + git_buf_sanitize(path); + return git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA); +} + int git_config__global_location(git_buf *buf) { const git_buf *paths; @@ -1132,6 +1139,10 @@ int git_config_open_default(git_config **out) error = git_config_add_file_ondisk(cfg, buf.ptr, GIT_CONFIG_LEVEL_SYSTEM, 0); + if (!error && !git_config_find_programdata(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_PROGRAMDATA, 0); + git_buf_free(&buf); if (error) { @@ -1144,6 +1155,41 @@ int git_config_open_default(git_config **out) return error; } +int git_config_lock(git_transaction **out, git_config *cfg) +{ + int error; + git_config_backend *file; + file_internal *internal; + + internal = git_vector_get(&cfg->files, 0); + if (!internal || !internal->file) { + giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files"); + return -1; + } + file = internal->file; + + if ((error = file->lock(file)) < 0) + return error; + + return git_transaction_config_new(out, cfg); +} + +int git_config_unlock(git_config *cfg, int commit) +{ + git_config_backend *file; + file_internal *internal; + + internal = git_vector_get(&cfg->files, 0); + if (!internal || !internal->file) { + giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files"); + return -1; + } + + file = internal->file; + + return file->unlock(file, commit); +} + /*********** * Parsers ***********/ diff --git a/src/config.h b/src/config.h index f257cc90f..00c12b50d 100644 --- a/src/config.h +++ b/src/config.h @@ -12,6 +12,7 @@ #include "vector.h" #include "repository.h" +#define GIT_CONFIG_FILENAME_PROGRAMDATA "config" #define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" #define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" #define GIT_CONFIG_FILENAME_XDG "config" @@ -88,4 +89,19 @@ extern int git_config__cvar( */ int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out, const git_cvar_map *maps, size_t map_n, int enum_val); + +/** + * Unlock the backend with the highest priority + * + * Unlocking will allow other writers to updat the configuration + * file. Optionally, any changes performed since the lock will be + * applied to the configuration. + * + * @param cfg the configuration + * @param commit boolean which indicates whether to commit any changes + * done since locking + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); + #endif diff --git a/src/config_file.c b/src/config_file.c index 52a5376bd..5f5e309e0 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -77,8 +77,7 @@ typedef struct git_config_file_iter { (iter) = (tmp)) struct reader { - time_t file_mtime; - size_t file_size; + git_oid checksum; char *file_path; git_buf buffer; char *read_ptr; @@ -105,6 +104,10 @@ typedef struct { git_array_t(struct reader) readers; + bool locked; + git_filebuf locked_buf; + git_buf locked_content; + char *file_path; } diskfile_backend; @@ -281,7 +284,7 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) git_buf_init(&reader->buffer, 0); res = git_futils_readbuffer_updated( - &reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL); + &reader->buffer, b->file_path, &reader->checksum, NULL); /* It's fine if the file doesn't exist */ if (res == GIT_ENOTFOUND) @@ -341,7 +344,7 @@ static int config_refresh(git_config_backend *cfg) reader = git_array_get(b->readers, i); error = git_futils_readbuffer_updated( &reader->buffer, reader->file_path, - &reader->file_mtime, &reader->file_size, &updated); + &reader->checksum, &updated); if (error < 0 && error != GIT_ENOTFOUND) return error; @@ -685,6 +688,42 @@ static int config_snapshot(git_config_backend **out, git_config_backend *in) return git_config_file__snapshot(out, b); } +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) + return error; + + error = git_futils_readbuffer(&cfg->locked_content, cfg->file_path); + if (error < 0 && error != GIT_ENOTFOUND) { + git_filebuf_cleanup(&cfg->locked_buf); + return error; + } + + cfg->locked = true; + return 0; + +} + +static int config_unlock(git_config_backend *_cfg, int success) +{ + diskfile_backend *cfg = (diskfile_backend *) _cfg; + int error = 0; + + if (success) { + git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); + error = git_filebuf_commit(&cfg->locked_buf); + } + + git_filebuf_cleanup(&cfg->locked_buf); + git_buf_free(&cfg->locked_content); + cfg->locked = false; + + return error; +} + int git_config_file__ondisk(git_config_backend **out, const char *path) { diskfile_backend *backend; @@ -706,6 +745,8 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->header.parent.del_multivar = config_delete_multivar; backend->header.parent.iterator = config_iterator_new; backend->header.parent.snapshot = config_snapshot; + backend->header.parent.lock = config_lock; + backend->header.parent.unlock = config_unlock; backend->header.parent.free = backend_free; *out = (git_config_backend *)backend; @@ -750,6 +791,21 @@ static int config_delete_readonly(git_config_backend *cfg, const char *name) return config_error_readonly(); } +static int config_lock_readonly(git_config_backend *_cfg) +{ + GIT_UNUSED(_cfg); + + return config_error_readonly(); +} + +static int config_unlock_readonly(git_config_backend *_cfg, int success) +{ + GIT_UNUSED(_cfg); + GIT_UNUSED(success); + + return config_error_readonly(); +} + static void backend_readonly_free(git_config_backend *_backend) { diskfile_backend *backend = (diskfile_backend *)_backend; @@ -803,6 +859,8 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) backend->header.parent.del = config_delete_readonly; backend->header.parent.del_multivar = config_delete_multivar_readonly; backend->header.parent.iterator = config_iterator_new; + backend->header.parent.lock = config_lock_readonly; + backend->header.parent.unlock = config_unlock_readonly; backend->header.parent.free = backend_readonly_free; *out = (git_config_backend *)backend; @@ -1559,7 +1617,7 @@ static int read_on_variable( git_buf_init(&r->buffer, 0); result = git_futils_readbuffer_updated( - &r->buffer, r->file_path, &r->file_mtime, &r->file_size, NULL); + &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); @@ -1602,7 +1660,7 @@ static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct re return config_parse(reader, NULL, read_on_variable, NULL, NULL, &parse_data); } -static int write_section(git_filebuf *file, const char *key) +static int write_section(git_buf *fbuf, const char *key) { int result; const char *dot; @@ -1626,7 +1684,7 @@ static int write_section(git_filebuf *file, const char *key) if (git_buf_oom(&buf)) return -1; - result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); + result = git_buf_put(fbuf, git_buf_cstr(&buf), buf.size); git_buf_free(&buf); return result; @@ -1651,7 +1709,8 @@ static const char *quotes_for_value(const char *value) } struct write_data { - git_filebuf *file; + git_buf *buf; + git_buf buffered_comment; unsigned int in_section : 1, preg_replaced : 1; const char *section; @@ -1660,23 +1719,28 @@ struct write_data { const char *value; }; -static int write_line(struct write_data *write_data, const char *line, size_t line_len) +static int write_line_to(git_buf *buf, const char *line, size_t line_len) { - int result = git_filebuf_write(write_data->file, line, line_len); + int result = git_buf_put(buf, line, line_len); if (!result && line_len && line[line_len-1] != '\n') - result = git_filebuf_printf(write_data->file, "\n"); + result = git_buf_printf(buf, "\n"); return result; } +static int write_line(struct write_data *write_data, const char *line, size_t line_len) +{ + return write_line_to(write_data->buf, line, line_len); +} + static int write_value(struct write_data *write_data) { const char *q; int result; q = quotes_for_value(write_data->value); - result = git_filebuf_printf(write_data->file, + result = git_buf_printf(write_data->buf, "\t%s = %s%s%s\n", write_data->name, q, write_data->value, q); /* If we are updating a single name/value, we're done. Setting `value` @@ -1711,6 +1775,14 @@ static int write_on_section( write_data->in_section = strcmp(current_section, write_data->section) == 0; + /* + * If there were comments just before this section, dump them as well. + */ + if (!result) { + result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); + git_buf_clear(&write_data->buffered_comment); + } + if (!result) result = write_line(write_data, line, line_len); @@ -1728,10 +1800,19 @@ static int write_on_variable( { struct write_data *write_data = (struct write_data *)data; bool has_matched = false; + int error; GIT_UNUSED(reader); GIT_UNUSED(current_section); + /* + * If there were comments just before this variable, let's dump them as well. + */ + if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return error; + + git_buf_clear(&write_data->buffered_comment); + /* See if we are to update this name/value pair; first examine name */ if (write_data->in_section && strcasecmp(write_data->name, var_name) == 0) @@ -1766,7 +1847,7 @@ static int write_on_comment(struct reader **reader, const char *line, size_t lin GIT_UNUSED(reader); write_data = (struct write_data *)data; - return write_line(write_data, line, line_len); + return write_line_to(&write_data->buffered_comment, line, line_len); } static int write_on_eof(struct reader **reader, void *data) @@ -1776,13 +1857,19 @@ static int write_on_eof(struct reader **reader, void *data) GIT_UNUSED(reader); + /* + * If we've buffered comments when reaching EOF, make sure to dump them. + */ + if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return result; + /* If we are at the EOF and have not written our value (again, for a * simple name/value set, not a multivar) then we have never seen the * section in question and should create a new section and write the * value. */ if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { - if ((result = write_section(write_data->file, write_data->section)) == 0) + if ((result = write_section(write_data->buf, write_data->section)) == 0) result = write_value(write_data); } @@ -1797,18 +1884,23 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p int result; char *section, *name, *ldot; git_filebuf file = GIT_FILEBUF_INIT; + git_buf buf = GIT_BUF_INIT; struct reader *reader = git_array_get(cfg->readers, 0); struct write_data write_data; - /* Lock the file */ - if ((result = git_filebuf_open( - &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) { + if (cfg->locked) { + result = git_buf_puts(&reader->buffer, 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); return result; - } + } - /* We need to read in our own config file */ - result = git_futils_readbuffer(&reader->buffer, cfg->file_path); + /* We need to read in our own config file */ + result = git_futils_readbuffer(&reader->buffer, cfg->file_path); + } /* Initialise the reading position */ if (result == GIT_ENOTFOUND) { @@ -1827,7 +1919,8 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p name = ldot + 1; section = git__strndup(key, ldot - key); - write_data.file = &file; + write_data.buf = &buf; + git_buf_init(&write_data.buffered_comment, 0); write_data.section = section; write_data.in_section = 0; write_data.preg_replaced = 0; @@ -1837,19 +1930,25 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data); git__free(section); + git_buf_free(&write_data.buffered_comment); if (result < 0) { git_filebuf_cleanup(&file); goto done; } - /* refresh stats - if this errors, then commit will error too */ - (void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file); - - result = git_filebuf_commit(&file); - git_buf_free(&reader->buffer); + if (cfg->locked) { + size_t len = buf.asize; + /* Update our copy with the modified contents */ + git_buf_free(&cfg->locked_content); + git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len); + } else { + git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf)); + result = git_filebuf_commit(&file); + } done: + git_buf_free(&buf); git_buf_free(&reader->buffer); return result; } diff --git a/src/config_file.h b/src/config_file.h index 0d8bf740f..1c52892c3 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -55,6 +55,16 @@ GIT_INLINE(int) git_config_file_foreach_match( return git_config_backend_foreach_match(cfg, regexp, fn, data); } +GIT_INLINE(int) git_config_file_lock(git_config_backend *cfg) +{ + return cfg->lock(cfg); +} + +GIT_INLINE(int) git_config_file_unlock(git_config_backend *cfg, int success) +{ + return cfg->unlock(cfg, success); +} + extern int git_config_file_normalize_section(char *start, char *end); #endif diff --git a/src/crlf.c b/src/crlf.c index f391137c1..5d7510ac7 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -346,7 +346,7 @@ static int crlf_apply( /* initialize payload in case `check` was bypassed */ if (!*payload) { int error = crlf_check(self, payload, src, NULL); - if (error < 0 && error != GIT_PASSTHROUGH) + if (error < 0) return error; } diff --git a/src/curl_stream.c b/src/curl_stream.c index 6534bdbbe..9963d94cc 100644 --- a/src/curl_stream.c +++ b/src/curl_stream.c @@ -67,9 +67,9 @@ static int curls_certificate(git_cert **out, git_stream *stream) /* No information is available, can happen with SecureTransport */ if (certinfo->num_of_certs == 0) { - s->cert_info.cert_type = GIT_CERT_NONE; - s->cert_info.data = NULL; - s->cert_info.len = 0; + s->cert_info.parent.cert_type = GIT_CERT_NONE; + s->cert_info.data = NULL; + s->cert_info.len = 0; return 0; } @@ -79,17 +79,18 @@ static int curls_certificate(git_cert **out, git_stream *stream) for (slist = certinfo->certinfo[0]; slist; slist = slist->next) { char *str = git__strdup(slist->data); GITERR_CHECK_ALLOC(str); + git_vector_insert(&strings, str); } /* Copy the contents of the vector into a strarray so we can expose them */ s->cert_info_strings.strings = (char **) strings.contents; s->cert_info_strings.count = strings.length; - s->cert_info.cert_type = GIT_CERT_STRARRAY; - s->cert_info.data = &s->cert_info_strings; - s->cert_info.len = strings.length; + s->cert_info.parent.cert_type = GIT_CERT_STRARRAY; + s->cert_info.data = &s->cert_info_strings; + s->cert_info.len = strings.length; - *out = (git_cert *) &s->cert_info; + *out = &s->cert_info.parent; return 0; } @@ -207,11 +208,14 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port) handle = curl_easy_init(); if (handle == NULL) { giterr_set(GITERR_NET, "failed to create curl handle"); + git__free(st); return -1; } - if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) + if ((error = git__strtol32(&iport, port, NULL, 10)) < 0) { + git__free(st); return error; + } curl_easy_setopt(handle, CURLOPT_URL, host); curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, st->curl_error); @@ -220,6 +224,7 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port) curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(handle, CURLOPT_CERTINFO, 1); curl_easy_setopt(handle, CURLOPT_HTTPPROXYTUNNEL, 1); + curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); /* curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); */ diff --git a/src/diff.c b/src/diff.c index c1adcc662..9ac5b9250 100644 --- a/src/diff.c +++ b/src/diff.c @@ -56,7 +56,7 @@ static int diff_insert_delta( if (diff->opts.notify_cb) { error = diff->opts.notify_cb( - diff, delta, matched_pathspec, diff->opts.notify_payload); + diff, delta, matched_pathspec, diff->opts.payload); if (error) { git__free(delta); @@ -74,6 +74,32 @@ static int diff_insert_delta( return error; } +static bool diff_pathspec_match( + const char **matched_pathspec, + git_diff *diff, + const git_index_entry *entry) +{ + bool disable_pathspec_match = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); + + /* If we're disabling fnmatch, then the iterator has already applied + * the filters to the files for us and we don't have to do anything. + * However, this only applies to *files* - the iterator will include + * directories that we need to recurse into when not autoexpanding, + * so we still need to apply the pathspec match to directories. + */ + if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && + disable_pathspec_match) { + *matched_pathspec = entry->path; + return true; + } + + return git_pathspec__match( + &diff->pathspec, entry->path, disable_pathspec_match, + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + matched_pathspec, NULL); +} + static int diff_delta__from_one( git_diff *diff, git_delta_t status, @@ -105,16 +131,12 @@ static int diff_delta__from_one( if (status == GIT_DELTA_UNTRACKED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) return 0; - + if (status == GIT_DELTA_UNREADABLE && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) return 0; - if (!git_pathspec__match( - &diff->pathspec, entry->path, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - &matched_pathspec, NULL)) + if (!diff_pathspec_match(&matched_pathspec, diff, entry)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -408,8 +430,9 @@ static git_diff *diff_list_alloc( diff->new_src = new_iter->type; memcpy(&diff->opts, &dflt, sizeof(diff->opts)); - if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 || - git_pool_init(&diff->pool, 1, 0) < 0) { + git_pool_init(&diff->pool, 1); + + if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0) { git_diff_free(diff); return NULL; } @@ -471,9 +494,6 @@ static int diff_list_apply_options( /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ - /* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */ - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS; - /* If not given explicit `opts`, check `diff.xyz` configs */ if (!opts) { int context = git_config__get_int_force(cfg, "diff.context", 3); @@ -674,13 +694,6 @@ int git_diff__oid_for_entry( return error; } -static bool diff_time_eq( - const git_index_time *a, const git_index_time *b, bool use_nanos) -{ - return a->seconds == b->seconds && - (!use_nanos || a->nanoseconds == b->nanoseconds); -} - typedef struct { git_repository *repo; git_iterator *old_iter; @@ -755,11 +768,7 @@ static int maybe_modified( const char *matched_pathspec; int error = 0; - if (!git_pathspec__match( - &diff->pathspec, oitem->path, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - &matched_pathspec, NULL)) + if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) return 0; memset(&noid, 0, sizeof(noid)); @@ -817,7 +826,6 @@ static int maybe_modified( */ } else if (git_oid_iszero(&nitem->id) && new_is_workdir) { bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); - bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0); git_index *index; git_iterator_index(&index, info->new_iter); @@ -836,13 +844,12 @@ static int maybe_modified( modified_uncertain = (oitem->file_size <= 0 && nitem->file_size > 0); } - else if (!diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) || - (use_ctime && - !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) || + else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || + (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || oitem->ino != nitem->ino || oitem->uid != nitem->uid || oitem->gid != nitem->gid || - (index && nitem->mtime.seconds >= index->stamp.mtime)) + git_index_entry_newer_than_index(nitem, index)) { status = GIT_DELTA_MODIFIED; modified_uncertain = true; @@ -1053,6 +1060,12 @@ static int handle_unmatched_new_item( &info->nitem, &untracked_state, info->new_iter)) < 0) return error; + /* if we found nothing that matched our pathlist filter, exclude */ + if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { + git_vector_pop(&diff->deltas); + git__free(last); + } + /* if we found nothing or just ignored items, update the record */ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || untracked_state == GIT_ITERATOR_STATUS_EMPTY) { @@ -1233,7 +1246,18 @@ int git_diff__from_iterators( /* run iterators building diffs */ while (!error && (info.oitem || info.nitem)) { - int cmp = info.oitem ? + int cmp; + + /* report progress */ + if (opts && opts->progress_cb) { + if ((error = opts->progress_cb(diff, + info.oitem ? info.oitem->path : NULL, + info.nitem ? info.nitem->path : NULL, + opts->payload))) + break; + } + + cmp = info.oitem ? (info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1; /* create DELETED records for old items not matched in new */ @@ -1264,11 +1288,26 @@ cleanup: return error; } -#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \ +#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ git_iterator *a = NULL, *b = NULL; \ - char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \ + char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \ + git_pathspec_prefix(&opts->pathspec) : NULL; \ + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \ + b_opts = GIT_ITERATOR_OPTIONS_INIT; \ + a_opts.flags = FLAGS_FIRST; \ + a_opts.start = pfx; \ + a_opts.end = pfx; \ + b_opts.flags = FLAGS_SECOND; \ + b_opts.start = pfx; \ + b_opts.end = pfx; \ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \ + a_opts.pathlist.strings = opts->pathspec.strings; \ + a_opts.pathlist.count = opts->pathspec.count; \ + b_opts.pathlist.strings = opts->pathspec.strings; \ + b_opts.pathlist.count = opts->pathspec.count; \ + } \ + if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ error = git_diff__from_iterators(diff, repo, a, b, opts); \ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ } while (0) @@ -1280,8 +1319,8 @@ int git_diff_tree_to_tree( git_tree *new_tree, const git_diff_options *opts) { - int error = 0; git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; + int error = 0; assert(diff && repo); @@ -1293,8 +1332,8 @@ int git_diff_tree_to_tree( iflag = GIT_ITERATOR_IGNORE_CASE; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx), - git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx) + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_tree(&b, new_tree, &b_opts), iflag ); return error; @@ -1318,10 +1357,10 @@ int git_diff_tree_to_index( git_index *index, const git_diff_options *opts) { - int error = 0; - bool index_ignore_case = false; git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_CONFLICTS; + bool index_ignore_case = false; + int error = 0; assert(diff && repo); @@ -1331,8 +1370,8 @@ int git_diff_tree_to_index( index_ignore_case = index->ignore_case; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx), - git_iterator_for_index(&b, index, iflag, pfx, pfx) + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_index(&b, repo, index, &b_opts), iflag ); /* if index is in case-insensitive order, re-sort deltas to match */ @@ -1356,10 +1395,11 @@ int git_diff_index_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_index( - &a, index, GIT_ITERATOR_INCLUDE_CONFLICTS, pfx, pfx), - git_iterator_for_workdir( - &b, repo, index, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) + git_iterator_for_index(&a, repo, index, &a_opts), + GIT_ITERATOR_INCLUDE_CONFLICTS, + + git_iterator_for_workdir(&b, repo, index, NULL, &b_opts), + GIT_ITERATOR_DONT_AUTOEXPAND ); if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX) && (*diff)->index_updated) @@ -1383,9 +1423,8 @@ int git_diff_tree_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), - git_iterator_for_workdir( - &b, repo, index, old_tree, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) + git_iterator_for_tree(&a, old_tree, &a_opts), 0, + git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND ); return error; @@ -1421,6 +1460,29 @@ int git_diff_tree_to_workdir_with_index( return error; } +int git_diff_index_to_index( + git_diff **diff, + git_repository *repo, + git_index *old_index, + git_index *new_index, + const git_diff_options *opts) +{ + int error = 0; + + assert(diff && old_index && new_index); + + DIFF_FROM_ITERATORS( + git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE, + git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE + ); + + /* if index is in case-insensitive order, re-sort deltas to match */ + if (!error && (old_index->ignore_case || new_index->ignore_case)) + diff_set_ignore_case(*diff, true); + + return error; +} + size_t git_diff_num_deltas(const git_diff *diff) { assert(diff); @@ -1597,6 +1659,7 @@ int git_diff_format_email__append_header_tobuf( const git_oid *id, const git_signature *author, const char *summary, + const char *body, size_t patch_no, size_t total_patches, bool exclude_patchno_marker) @@ -1636,6 +1699,13 @@ int git_diff_format_email__append_header_tobuf( error = git_buf_printf(out, "%s\n\n", summary); + if (body) { + git_buf_puts(out, body); + + if (out->ptr[out->size - 1] != '\n') + git_buf_putc(out, '\n'); + } + return error; } @@ -1713,7 +1783,7 @@ int git_diff_format_email( error = git_diff_format_email__append_header_tobuf(out, opts->id, opts->author, summary == NULL ? opts->summary : summary, - opts->patch_no, opts->total_patches, ignore_marker); + opts->body, opts->patch_no, opts->total_patches, ignore_marker); if (error < 0) goto on_error; @@ -1756,6 +1826,7 @@ int git_diff_commit_as_email( opts.total_patches = total_patches; opts.id = git_commit_id(commit); opts.summary = git_commit_summary(commit); + opts.body = git_commit_body(commit); opts.author = git_commit_author(commit); if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) diff --git a/src/diff.h b/src/diff.h index 2dfc2c615..47743f88b 100644 --- a/src/diff.h +++ b/src/diff.h @@ -28,7 +28,6 @@ enum { GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ - GIT_DIFFCAPS_TRUST_NANOSECS = (1 << 5), /* use stat time nanoseconds */ }; #define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) diff --git a/src/diff_driver.c b/src/diff_driver.c index 9d1337103..bc3518991 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -97,8 +97,7 @@ static int diff_driver_add_patterns( for (scan = regex_str; scan; scan = end) { /* get pattern to fill in */ if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) { - error = -1; - break; + return -1; } pat->flags = regex_flags; @@ -117,10 +116,9 @@ static int diff_driver_add_patterns( break; if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) != 0) { - /* if regex fails to compile, warn? fail? */ - error = giterr_set_regex(&pat->re, error); - regfree(&pat->re); - break; + /* + * TODO: issue a warning + */ } } @@ -128,7 +126,8 @@ static int diff_driver_add_patterns( (void)git_array_pop(drv->fn_patterns); /* release last item */ git_buf_free(&buf); - return error; + /* We want to ignore bad patterns, so return success regardless */ + return 0; } static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) diff --git a/src/diff_file.c b/src/diff_file.c index c60362865..ecc34cf55 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -259,10 +259,35 @@ static int diff_file_content_load_blob( return error; } +static int diff_file_content_load_workdir_symlink_fake( + git_diff_file_content *fc, git_buf *path) +{ + git_buf target = GIT_BUF_INIT; + int error; + + if ((error = git_futils_readbuffer(&target, path->ptr)) < 0) + return error; + + fc->map.len = git_buf_len(&target); + fc->map.data = git_buf_detach(&target); + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + git_buf_free(&target); + return error; +} + static int diff_file_content_load_workdir_symlink( git_diff_file_content *fc, git_buf *path) { ssize_t alloc_len, read_len; + int symlink_supported, error; + + if ((error = git_repository__cvar( + &symlink_supported, fc->repo, GIT_CVAR_SYMLINKS)) < 0) + return -1; + + if (!symlink_supported) + return diff_file_content_load_workdir_symlink_fake(fc, path); /* link path on disk could be UTF-16, so prepare a buffer that is * big enough to handle some UTF-8 data expansion diff --git a/src/diff_patch.c b/src/diff_patch.c index 0628da6f2..50faa3b3f 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -30,6 +30,10 @@ static void diff_patch_update_binary(git_patch *patch) (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) patch->delta->flags |= GIT_DIFF_FLAG_BINARY; + else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || + patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) + patch->delta->flags |= GIT_DIFF_FLAG_BINARY; + else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; diff --git a/src/diff_print.c b/src/diff_print.c index d406a441a..dae9e341d 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -92,7 +92,11 @@ static int diff_print_info_init_frompatch( git_diff_line_cb cb, void *payload) { - git_repository *repo = patch && patch->diff ? patch->diff->repo : NULL; + git_repository *repo; + + assert(patch); + + repo = patch->diff ? patch->diff->repo : NULL; memset(pi, 0, sizeof(diff_print_info)); @@ -358,6 +362,7 @@ static int format_binary( scan += chunk_len; pi->line.num_lines++; } + git_buf_putc(pi->buf, '\n'); return 0; } @@ -416,7 +421,6 @@ static int diff_print_patch_file_binary( if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || - (error = git_buf_putc(pi->buf, '\n')) < 0 || (error = format_binary(pi, binary->old_file.type, binary->old_file.data, binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { diff --git a/src/diff_tform.c b/src/diff_tform.c index 92647e330..8577f06b8 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -134,11 +134,11 @@ int git_diff__merge( return -1; } - if (git_vector_init( - &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || - git_pool_init(&onto_pool, 1, 0) < 0) + if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0) return -1; + git_pool_init(&onto_pool, 1); + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); @@ -261,18 +261,23 @@ static int normalize_find_opts( if (!given || (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) { - char *rule = - git_config__get_string_force(cfg, "diff.renames", "true"); - int boolval; + if (diff->repo) { + char *rule = + git_config__get_string_force(cfg, "diff.renames", "true"); + int boolval; - if (!git__parse_bool(&boolval, rule) && !boolval) - /* don't set FIND_RENAMES if bool value is false */; - else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) - opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - else + if (!git__parse_bool(&boolval, rule) && !boolval) + /* don't set FIND_RENAMES if bool value is false */; + else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) + opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + else + opts->flags |= GIT_DIFF_FIND_RENAMES; + + git__free(rule); + } else { + /* set default flag */ opts->flags |= GIT_DIFF_FIND_RENAMES; - - git__free(rule); + } } /* some flags imply others */ diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c index e5984f1c9..1057df3aa 100644 --- a/src/diff_xdiff.c +++ b/src/diff_xdiff.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 "git2/errors.h" #include "common.h" #include "diff.h" #include "diff_driver.h" @@ -208,6 +209,12 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); git_patch__new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch); + if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || + info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { + giterr_set(GITERR_INVALID, "files too large for diff"); + return -1; + } + xdl_diff(&info.xd_old_data, &info.xd_new_data, &xo->params, &xo->config, &xo->callback); diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h index c547b00cf..98e11b2cb 100644 --- a/src/diff_xdiff.h +++ b/src/diff_xdiff.h @@ -11,6 +11,11 @@ #include "diff_patch.h" #include "xdiff/xdiff.h" +/* xdiff cannot cope with large files. these files should not be passed to + * xdiff. callers should treat these large files as binary. + */ +#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023) + /* A git_xdiff_output is a git_diff_output with extra fields necessary * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field * of the output to use xdiff to generate the diffs. diff --git a/src/errors.c b/src/errors.c index 7a2600586..91acc3541 100644 --- a/src/errors.c +++ b/src/errors.c @@ -18,19 +18,30 @@ static git_error g_git_oom_error = { GITERR_NOMEMORY }; -static void set_error(int error_class, char *string) +static void set_error_from_buffer(int error_class) { git_error *error = &GIT_GLOBAL->error_t; + git_buf *buf = &GIT_GLOBAL->error_buf; - if (error->message != string) - git__free(error->message); - - error->message = string; + error->message = buf->ptr; error->klass = error_class; GIT_GLOBAL->last_error = error; } +static void set_error(int error_class, char *string) +{ + git_buf *buf = &GIT_GLOBAL->error_buf; + + git_buf_clear(buf); + if (string) { + git_buf_puts(buf, string); + git__free(string); + } + + set_error_from_buffer(error_class); +} + void giterr_set_oom(void) { GIT_GLOBAL->last_error = &g_git_oom_error; @@ -38,27 +49,28 @@ void giterr_set_oom(void) void giterr_set(int error_class, const char *string, ...) { - git_buf buf = GIT_BUF_INIT; va_list arglist; #ifdef GIT_WIN32 DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0; #endif int error_code = (error_class == GITERR_OS) ? errno : 0; + git_buf *buf = &GIT_GLOBAL->error_buf; + git_buf_clear(buf); if (string) { va_start(arglist, string); - git_buf_vprintf(&buf, string, arglist); + git_buf_vprintf(buf, string, arglist); va_end(arglist); if (error_class == GITERR_OS) - git_buf_PUTS(&buf, ": "); + git_buf_PUTS(buf, ": "); } if (error_class == GITERR_OS) { #ifdef GIT_WIN32 char * win32_error = git_win32_get_error_message(win32_error_code); if (win32_error) { - git_buf_puts(&buf, win32_error); + git_buf_puts(buf, win32_error); git__free(win32_error); SetLastError(0); @@ -66,26 +78,29 @@ void giterr_set(int error_class, const char *string, ...) else #endif if (error_code) - git_buf_puts(&buf, strerror(error_code)); + git_buf_puts(buf, strerror(error_code)); if (error_code) errno = 0; } - if (!git_buf_oom(&buf)) - set_error(error_class, git_buf_detach(&buf)); + if (!git_buf_oom(buf)) + set_error_from_buffer(error_class); } void giterr_set_str(int error_class, const char *string) { - char *message; + git_buf *buf = &GIT_GLOBAL->error_buf; assert(string); - message = git__strdup(string); + if (!string) + return; - if (message) - set_error(error_class, message); + git_buf_clear(buf); + git_buf_puts(buf, string); + if (!git_buf_oom(buf)) + set_error_from_buffer(error_class); } int giterr_set_regex(const regex_t *regex, int error_code) @@ -116,45 +131,65 @@ void giterr_clear(void) #endif } -int giterr_detach(git_error *cpy) -{ - git_error *error = GIT_GLOBAL->last_error; - - assert(cpy); - - if (!error) - return -1; - - cpy->message = error->message; - cpy->klass = error->klass; - - error->message = NULL; - giterr_clear(); - - return 0; -} - const git_error *giterr_last(void) { return GIT_GLOBAL->last_error; } -int giterr_capture(git_error_state *state, int error_code) +int giterr_state_capture(git_error_state *state, int error_code) { + git_error *error = GIT_GLOBAL->last_error; + git_buf *error_buf = &GIT_GLOBAL->error_buf; + + memset(state, 0, sizeof(git_error_state)); + + if (!error_code) + return 0; + state->error_code = error_code; - if (error_code) - giterr_detach(&state->error_msg); + state->oom = (error == &g_git_oom_error); + + if (error) { + state->error_msg.klass = error->klass; + + if (state->oom) + state->error_msg.message = g_git_oom_error.message; + else + state->error_msg.message = git_buf_detach(error_buf); + } + + giterr_clear(); return error_code; } -int giterr_restore(git_error_state *state) +int giterr_state_restore(git_error_state *state) { - if (state && state->error_code && state->error_msg.message) - set_error(state->error_msg.klass, state->error_msg.message); - else - giterr_clear(); + int ret = 0; - return state ? state->error_code : 0; + giterr_clear(); + + if (state && state->error_msg.message) { + if (state->oom) + giterr_set_oom(); + else + set_error(state->error_msg.klass, state->error_msg.message); + + ret = state->error_code; + memset(state, 0, sizeof(git_error_state)); + } + + return ret; +} + +void giterr_state_free(git_error_state *state) +{ + if (!state) + return; + + if (!state->oom) + git__free(state->error_msg.message); + + memset(state, 0, sizeof(git_error_state)); } int giterr_system_last(void) diff --git a/src/filebuf.c b/src/filebuf.c index 838f4b4d2..17efe872e 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -191,6 +191,81 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) return 0; } +#define MAX_SYMLINK_DEPTH 5 + +static int resolve_symlink(git_buf *out, const char *path) +{ + int i, error, root; + ssize_t ret; + struct stat st; + git_buf curpath = GIT_BUF_INIT, target = GIT_BUF_INIT; + + if ((error = git_buf_grow(&target, GIT_PATH_MAX + 1)) < 0 || + (error = git_buf_puts(&curpath, path)) < 0) + return error; + + for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { + error = p_lstat(curpath.ptr, &st); + if (error < 0 && errno == ENOENT) { + error = git_buf_puts(out, curpath.ptr); + goto cleanup; + } + + if (error < 0) { + giterr_set(GITERR_OS, "failed to stat '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (!S_ISLNK(st.st_mode)) { + error = git_buf_puts(out, curpath.ptr); + goto cleanup; + } + + ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); + if (ret < 0) { + giterr_set(GITERR_OS, "failed to read symlink '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (ret == GIT_PATH_MAX) { + giterr_set(GITERR_INVALID, "symlink target too long"); + error = -1; + goto cleanup; + } + + /* readlink(2) won't NUL-terminate for us */ + target.ptr[ret] = '\0'; + target.size = ret; + + root = git_path_root(target.ptr); + if (root >= 0) { + if ((error = git_buf_puts(&curpath, target.ptr)) < 0) + goto cleanup; + } else { + git_buf dir = GIT_BUF_INIT; + + if ((error = git_path_dirname_r(&dir, curpath.ptr)) < 0) + goto cleanup; + + git_buf_swap(&curpath, &dir); + git_buf_free(&dir); + + if ((error = git_path_apply_relative(&curpath, target.ptr)) < 0) + goto cleanup; + } + } + + giterr_set(GITERR_INVALID, "maximum symlink depth reached"); + error = -1; + +cleanup: + git_buf_free(&curpath); + git_buf_free(&target); + return error; +} + int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) { int compression, error = -1; @@ -265,11 +340,14 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode file->path_lock = git_buf_detach(&tmp_path); GITERR_CHECK_ALLOC(file->path_lock); } else { - path_len = strlen(path); + git_buf resolved_path = GIT_BUF_INIT; + + if ((error = resolve_symlink(&resolved_path, path)) < 0) + goto cleanup; /* Save the original path of the file */ - file->path_original = git__strdup(path); - GITERR_CHECK_ALLOC(file->path_original); + path_len = resolved_path.size; + file->path_original = git_buf_detach(&resolved_path); /* create the locking path by appending ".lock" to the original */ GITERR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); @@ -279,6 +357,12 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode memcpy(file->path_lock, file->path_original, path_len); memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); + if (git_path_isdir(file->path_original)) { + giterr_set(GITERR_FILESYSTEM, "path '%s' is a directory", file->path_original); + error = GIT_EDIRECTORY; + goto cleanup; + } + /* open the file for locking */ if ((error = lock_file(file, flags, mode)) < 0) goto cleanup; diff --git a/src/fileops.c b/src/fileops.c index b7b55159f..22868b489 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -18,7 +18,7 @@ GIT__USE_STRMAP int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( - file_path, NULL, mode, + file_path, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } @@ -153,13 +153,15 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) } int git_futils_readbuffer_updated( - git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) + git_buf *out, const char *path, git_oid *checksum, int *updated) { + int error; git_file fd; struct stat st; - bool changed = false; + git_buf buf = GIT_BUF_INIT; + git_oid checksum_new; - assert(buf && path && *path); + assert(out && path && *path); if (updated != NULL) *updated = 0; @@ -178,45 +180,50 @@ int git_futils_readbuffer_updated( return -1; } - /* - * If we were given a time and/or a size, we only want to read the file - * if it has been modified. - */ - if (size && *size != (size_t)st.st_size) - changed = true; - if (mtime && *mtime != (time_t)st.st_mtime) - changed = true; - if (!size && !mtime) - changed = true; - - if (!changed) { - return 0; - } - - if (mtime != NULL) - *mtime = st.st_mtime; - if (size != NULL) - *size = (size_t)st.st_size; - if ((fd = git_futils_open_ro(path)) < 0) return fd; - if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { + if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; } p_close(fd); + if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) { + git_buf_free(&buf); + return error; + } + + /* + * If we were given a checksum, we only want to use it if it's different + */ + if (checksum && !git_oid__cmp(checksum, &checksum_new)) { + git_buf_free(&buf); + if (updated) + *updated = 0; + + return 0; + } + + /* + * If we're here, the file did change, or the user didn't have an old version + */ + if (checksum) + git_oid_cpy(checksum, &checksum_new); + if (updated != NULL) *updated = 1; + git_buf_swap(out, &buf); + git_buf_free(&buf); + return 0; } int git_futils_readbuffer(git_buf *buf, const char *path) { - return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); + return git_futils_readbuffer_updated(buf, path, NULL, NULL); } int git_futils_writebuffer( @@ -289,96 +296,229 @@ void git_futils_mmap_free(git_map *out) p_munmap(out); } -GIT_INLINE(int) validate_existing( - const char *make_path, +GIT_INLINE(int) mkdir_validate_dir( + const char *path, struct stat *st, mode_t mode, uint32_t flags, - struct git_futils_mkdir_perfdata *perfdata) + struct git_futils_mkdir_options *opts) { + /* with exclusive create, existing dir is an error */ + if ((flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_FILESYSTEM, + "Failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(make_path) < 0) { + if (p_unlink(path) < 0) { giterr_set(GITERR_OS, "Failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", make_path); + S_ISLNK(st->st_mode) ? "symlink" : "file", path); return GIT_EEXISTS; } - perfdata->mkdir_calls++; + opts->perfdata.mkdir_calls++; - if (p_mkdir(make_path, mode) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_mkdir(path, mode) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (S_ISLNK(st->st_mode)) { /* Re-stat the target, make sure it's a directory */ - perfdata->stat_calls++; + opts->perfdata.stat_calls++; - if (p_stat(make_path, st) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_stat(path, st) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (!S_ISDIR(st->st_mode)) { giterr_set(GITERR_FILESYSTEM, - "Failed to make directory '%s': directory exists", make_path); + "Failed to make directory '%s': directory exists", path); return GIT_EEXISTS; } return 0; } -int git_futils_mkdir_ext( +GIT_INLINE(int) mkdir_validate_mode( const char *path, + struct stat *st, + bool terminal_path, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { + + opts->perfdata.chmod_calls++; + + if (p_chmod(path, mode) < 0) { + giterr_set(GITERR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} + +GIT_INLINE(int) mkdir_canonicalize( + git_buf *path, + uint32_t flags) +{ + ssize_t root_len; + + if (path->size == 0) { + giterr_set(GITERR_OS, "attempt to create empty path"); + return -1; + } + + /* Trim trailing slashes (except the root) */ + if ((root_len = git_path_root(path->ptr)) < 0) + root_len = 0; + else + root_len++; + + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; + + /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { + git_path_dirname_r(path, path->ptr); + flags |= GIT_MKDIR_SKIP_LAST; + } + if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { + git_path_dirname_r(path, path->ptr); + } + + /* We were either given the root path (or trimmed it to + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_buf_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, root_len, error; + + if ((error = git_buf_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_buf_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + root_len = git_path_root(make_path.ptr); + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. + */ + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + giterr_set(GITERR_OS, "failed to stat '%s'", parent_path.ptr); + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + assert(len); + + /* we've walked all the given path's parents and it's either relative + * or rooted. either way, give up and make the entire path. + */ + if ((len == 1 && parent_path.ptr[0] == '.') || len == root_len+1) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); + + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); + + goto done; + } + + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_buf_free(&make_path); + git_buf_free(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts) { - int error = -1; git_buf make_path = GIT_BUF_INIT; - ssize_t root = 0, min_root_len, root_len; + ssize_t root = 0, min_root_len; char lastch = '/', *tail; struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; /* build path and find "root" where we should start calling mkdir */ - if (git_path_join_unrooted(&make_path, path, base, &root) < 0) + if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) return -1; - if (make_path.size == 0) { - giterr_set(GITERR_OS, "Attempt to create empty path"); + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) goto done; - } - - /* Trim trailing slashes (except the root) */ - if ((root_len = git_path_root(make_path.ptr)) < 0) - root_len = 0; - else - root_len++; - - while (make_path.size > (size_t)root_len && - make_path.ptr[make_path.size - 1] == '/') - make_path.ptr[--make_path.size] = '\0'; - - /* if we are not supposed to made the last element, truncate it */ - if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); - flags |= GIT_MKDIR_SKIP_LAST; - } - if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); - } - - /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. - */ - if (make_path.size <= (size_t)root_len) { - error = 0; - goto done; - } /* if we are not supposed to make the whole path, reset root */ if ((flags & GIT_MKDIR_PATH) == 0) @@ -437,32 +577,15 @@ retry_lstat: goto done; } } else { - /* with exclusive create, existing dir is an error */ - if ((flags & GIT_MKDIR_EXCL) != 0) { - giterr_set(GITERR_FILESYSTEM, "Failed to make directory '%s': directory exists", make_path.ptr); - error = GIT_EEXISTS; + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) goto done; - } - - if ((error = validate_existing( - make_path.ptr, &st, mode, flags, &opts->perfdata)) < 0) - goto done; } /* chmod if requested and necessary */ - if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || - (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && - st.st_mode != mode) { - - opts->perfdata.chmod_calls++; - - if ((error = p_chmod(make_path.ptr, mode)) < 0 && - lastch == '\0') { - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", - make_path.ptr); - goto done; - } - } + if ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; if (opts->dir_map && opts->pool) { char *cache_path; @@ -501,21 +624,6 @@ done: return error; } -int git_futils_mkdir( - const char *path, - const char *base, - mode_t mode, - uint32_t flags) -{ - struct git_futils_mkdir_options options = {0}; - return git_futils_mkdir_ext(path, base, mode, flags, &options); -} - -int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) -{ - return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH); -} - typedef struct { const char *base; size_t baselen; @@ -777,7 +885,7 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create root directory the first time we need to create a directory */ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { error = git_futils_mkdir( - info->to_root, NULL, info->dirmode, + info->to_root, info->dirmode, (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; @@ -785,9 +893,9 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create directory with root as base to prevent excess chmods */ if (!error) - error = git_futils_mkdir( + error = git_futils_mkdir_relative( from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags); + info->dirmode, info->mkdir_flags, NULL); return error; } @@ -934,12 +1042,18 @@ int git_futils_filestamp_check( if (p_stat(path, &st) < 0) return GIT_ENOTFOUND; - if (stamp->mtime == (git_time_t)st.st_mtime && + if (stamp->mtime.tv_sec == st.st_mtime && +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec == st.st_mtime_nsec && +#endif stamp->size == (git_off_t)st.st_size && stamp->ino == (unsigned int)st.st_ino) return 0; - stamp->mtime = (git_time_t)st.st_mtime; + stamp->mtime.tv_sec = st.st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st.st_mtime_nsec; +#endif stamp->size = (git_off_t)st.st_size; stamp->ino = (unsigned int)st.st_ino; @@ -962,7 +1076,12 @@ void git_futils_filestamp_set_from_stat( git_futils_filestamp *stamp, struct stat *st) { if (st) { - stamp->mtime = (git_time_t)st->st_mtime; + stamp->mtime.tv_sec = st->st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st->st_mtime_nsec; +#else + stamp->mtime.tv_nsec = 0; +#endif stamp->size = (git_off_t)st->st_size; stamp->ino = (unsigned int)st->st_ino; } else { diff --git a/src/fileops.h b/src/fileops.h index 0f6466c59..6c6c49dcf 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -13,6 +13,7 @@ #include "path.h" #include "pool.h" #include "strmap.h" +#include "oid.h" /** * Filebuffer methods @@ -21,7 +22,7 @@ */ extern int git_futils_readbuffer(git_buf *obj, const char *path); extern int git_futils_readbuffer_updated( - git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated); + git_buf *obj, const char *path, git_oid *checksum, int *updated); extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); extern int git_futils_writebuffer( @@ -55,12 +56,9 @@ extern int git_futils_creat_locked(const char *path, const mode_t mode); extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); /** - * Create a path recursively - * - * If a base parameter is being passed, it's expected to be valued with a - * path pointing to an already existing directory. + * Create a path recursively. */ -extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); +extern int git_futils_mkdir_r(const char *path, const mode_t mode); /** * Flags to pass to `git_futils_mkdir`. @@ -111,20 +109,20 @@ struct git_futils_mkdir_options * and optionally chmods the directory immediately after (or each part of the * path if requested). * - * @param path The path to create. + * @param path The path to create, relative to base. * @param base Root for relative path. These directories will never be made. * @param mode The mode to use for created directories. * @param flags Combination of the mkdir flags above. - * @param opts Extended options, use `git_futils_mkdir` if you are not interested. + * @param opts Extended options, or null. * @return 0 on success, else error code */ -extern int git_futils_mkdir_ext(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); /** - * Create a directory or entire path. Similar to `git_futils_mkdir_withperf` + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` * without performance data. */ -extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags); +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); /** * Create all the folders required to contain @@ -312,7 +310,7 @@ extern int git_futils_fake_symlink(const char *new, const char *old); * versions could be implemented in the future. */ typedef struct { - git_time_t mtime; + struct timespec mtime; git_off_t size; unsigned int ino; } git_futils_filestamp; diff --git a/src/filter.c b/src/filter.c index 4006351f8..a0628d779 100644 --- a/src/filter.c +++ b/src/filter.c @@ -56,80 +56,15 @@ static int filter_def_priority_cmp(const void *a, const void *b) return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; } -struct filter_registry { +struct git_filter_registry { + git_rwlock lock; git_vector filters; }; -static struct filter_registry *git__filter_registry = NULL; +static struct git_filter_registry filter_registry; -static void filter_registry_shutdown(void) -{ - struct filter_registry *reg = NULL; - size_t pos; - git_filter_def *fdef; +static void git_filter_global_shutdown(void); - if ((reg = git__swap(git__filter_registry, NULL)) == NULL) - return; - - git_vector_foreach(®->filters, pos, fdef) { - if (fdef->filter && fdef->filter->shutdown) { - fdef->filter->shutdown(fdef->filter); - fdef->initialized = false; - } - - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - } - - git_vector_free(®->filters); - git__free(reg); -} - -static int filter_registry_initialize(void) -{ - int error = 0; - struct filter_registry *reg; - - if (git__filter_registry) - return 0; - - reg = git__calloc(1, sizeof(struct filter_registry)); - GITERR_CHECK_ALLOC(reg); - - if ((error = git_vector_init( - ®->filters, 2, filter_def_priority_cmp)) < 0) - goto cleanup; - - reg = git__compare_and_swap(&git__filter_registry, NULL, reg); - if (reg != NULL) - goto cleanup; - - git__on_shutdown(filter_registry_shutdown); - - /* try to register both default filters */ - { - git_filter *crlf = git_crlf_filter_new(); - git_filter *ident = git_ident_filter_new(); - - if (crlf && git_filter_register( - GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0) - crlf = NULL; - if (ident && git_filter_register( - GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) - ident = NULL; - - if (!crlf || !ident) - return -1; - } - - return 0; - -cleanup: - git_vector_free(®->filters); - git__free(reg); - return error; -} static int filter_def_scan_attrs( git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) @@ -210,40 +145,14 @@ static int filter_def_filter_key_check(const void *key, const void *fdef) return (key == filter) ? 0 : -1; } -static int filter_registry_find(size_t *pos, const char *name) -{ - return git_vector_search2( - pos, &git__filter_registry->filters, filter_def_name_key_check, name); -} - -static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) -{ - git_filter_def *fdef = NULL; - - if (!filter_registry_find(pos, name)) - fdef = git_vector_get(&git__filter_registry->filters, *pos); - - return fdef; -} - -int git_filter_register( +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_insert( const char *name, git_filter *filter, int priority) { git_filter_def *fdef; size_t nattr = 0, nmatch = 0, alloc_len; git_buf attrs = GIT_BUF_INIT; - assert(name && filter); - - if (filter_registry_initialize() < 0) - return -1; - - if (!filter_registry_find(NULL, name)) { - giterr_set( - GITERR_FILTER, "Attempt to reregister existing filter '%s'", name); - return GIT_EEXISTS; - } - if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) return -1; @@ -265,21 +174,123 @@ int git_filter_register( filter_def_set_attrs(fdef); - if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) { + if (git_vector_insert(&filter_registry.filters, fdef) < 0) { git__free(fdef->filter_name); git__free(fdef->attrdata); git__free(fdef); return -1; } - git_vector_sort(&git__filter_registry->filters); + git_vector_sort(&filter_registry.filters); return 0; } +int git_filter_global_init(void) +{ + git_filter *crlf = NULL, *ident = NULL; + int error = 0; + + if (git_rwlock_init(&filter_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&filter_registry.filters, 2, + filter_def_priority_cmp)) < 0) + goto done; + + if ((crlf = git_crlf_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || + (ident = git_ident_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) + error = -1; + + git__on_shutdown(git_filter_global_shutdown); + +done: + if (error) { + git_filter_free(crlf); + git_filter_free(ident); + } + + return error; +} + +static void git_filter_global_shutdown(void) +{ + size_t pos; + git_filter_def *fdef; + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) + return; + + git_vector_foreach(&filter_registry.filters, pos, fdef) { + if (fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + } + + git_vector_free(&filter_registry.filters); + + git_rwlock_wrunlock(&filter_registry.lock); + git_rwlock_free(&filter_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2( + pos, &filter_registry.filters, filter_def_name_key_check, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) +{ + git_filter_def *fdef = NULL; + + if (!filter_registry_find(pos, name)) + fdef = git_vector_get(&filter_registry.filters, *pos); + + return fdef; +} + + +int git_filter_register( + const char *name, git_filter *filter, int priority) +{ + int error; + + assert(name && filter); + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock filter registry"); + return -1; + } + + if (!filter_registry_find(NULL, name)) { + giterr_set( + GITERR_FILTER, "attempt to reregister existing filter '%s'", name); + error = GIT_EEXISTS; + goto done; + } + + error = filter_registry_insert(name, filter, priority); + +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; +} + int git_filter_unregister(const char *name) { size_t pos; git_filter_def *fdef; + int error = 0; assert(name); @@ -289,12 +300,18 @@ int git_filter_unregister(const char *name) return -1; } - if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { - giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name); - return GIT_ENOTFOUND; + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock filter registry"); + return -1; } - (void)git_vector_remove(&git__filter_registry->filters, pos); + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { + giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&filter_registry.filters, pos); if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { fdef->filter->shutdown(fdef->filter); @@ -305,21 +322,18 @@ int git_filter_unregister(const char *name) git__free(fdef->attrdata); git__free(fdef); - return 0; +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; } static int filter_initialize(git_filter_def *fdef) { int error = 0; - if (!fdef->initialized && - fdef->filter && - fdef->filter->initialize && - (error = fdef->filter->initialize(fdef->filter)) < 0) - { - /* auto-unregister if initialize fails */ - git_filter_unregister(fdef->filter_name); - return error; + if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { + if ((error = fdef->filter->initialize(fdef->filter)) < 0) + return error; } fdef->initialized = true; @@ -330,17 +344,22 @@ git_filter *git_filter_lookup(const char *name) { size_t pos; git_filter_def *fdef; + git_filter *filter = NULL; - if (filter_registry_initialize() < 0) + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock filter registry"); return NULL; + } - if ((fdef = filter_registry_lookup(&pos, name)) == NULL) - return NULL; + if ((fdef = filter_registry_lookup(&pos, name)) == NULL || + (!fdef->initialized && filter_initialize(fdef) < 0)) + goto done; - if (!fdef->initialized && filter_initialize(fdef) < 0) - return NULL; + filter = fdef->filter; - return fdef->filter; +done: + git_rwlock_rdunlock(&filter_registry.lock); + return filter; } void git_filter_free(git_filter *filter) @@ -433,8 +452,11 @@ static int filter_list_check_attributes( want_type = git_attr_value(want); found_type = git_attr_value(strs[i]); - if (want_type != found_type || - (want_type == GIT_ATTR_VALUE_T && strcmp(want, strs[i]))) + if (want_type != found_type) + error = GIT_ENOTFOUND; + else if (want_type == GIT_ATTR_VALUE_T && + strcmp(want, strs[i]) && + strcmp(want, "*")) error = GIT_ENOTFOUND; } @@ -475,8 +497,10 @@ int git_filter_list__load_ext( size_t idx; git_filter_def *fdef; - if (filter_registry_initialize() < 0) + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock filter registry"); return -1; + } src.repo = repo; src.path = path; @@ -486,7 +510,7 @@ int git_filter_list__load_ext( if (blob) git_oid_cpy(&src.oid, git_blob_id(blob)); - git_vector_foreach(&git__filter_registry->filters, idx, fdef) { + git_vector_foreach(&filter_registry.filters, idx, fdef) { const char **values = NULL; void *payload = NULL; @@ -520,7 +544,7 @@ int git_filter_list__load_ext( else { if (!fl) { if ((error = filter_list_new(&fl, &src)) < 0) - return error; + break; fl->temp_buf = filter_opts->temp_buf; } @@ -534,6 +558,8 @@ int git_filter_list__load_ext( } } + git_rwlock_rdunlock(&filter_registry.lock); + if (error && fl != NULL) { git_array_clear(fl->filters); git__free(fl); @@ -601,19 +627,27 @@ int git_filter_list_push( { int error = 0; size_t pos; - git_filter_def *fdef; + git_filter_def *fdef = NULL; git_filter_entry *fe; assert(fl && filter); - if (git_vector_search2( - &pos, &git__filter_registry->filters, - filter_def_filter_key_check, filter) < 0) { - giterr_set(GITERR_FILTER, "Cannot use an unregistered filter"); + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock filter registry"); return -1; } - fdef = git_vector_get(&git__filter_registry->filters, pos); + if (git_vector_search2( + &pos, &filter_registry.filters, + filter_def_filter_key_check, filter) == 0) + fdef = git_vector_get(&filter_registry.filters, pos); + + git_rwlock_rdunlock(&filter_registry.lock); + + if (fdef == NULL) { + giterr_set(GITERR_FILTER, "Cannot use an unregistered filter"); + return -1; + } if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) return error; diff --git a/src/filter.h b/src/filter.h index 5062afba5..9bd835f94 100644 --- a/src/filter.h +++ b/src/filter.h @@ -32,6 +32,8 @@ typedef struct { #define GIT_FILTER_OPTIONS_INIT {0} +extern int git_filter_global_init(void); + extern void git_filter_free(git_filter *filter); extern int git_filter_list__load_ext( diff --git a/src/global.c b/src/global.c index 3f20bfd31..0bfde1e04 100644 --- a/src/global.c +++ b/src/global.c @@ -8,26 +8,25 @@ #include "global.h" #include "hash.h" #include "sysdir.h" -#include "git2/global.h" -#include "git2/sys/openssl.h" +#include "filter.h" +#include "openssl_stream.h" #include "thread-utils.h" +#include "git2/global.h" +#include "transports/ssh.h" +#if defined(GIT_MSVC_CRTDBG) +#include "win32/w32_stack.h" +#include "win32/w32_crtdbg_stacktrace.h" +#endif git_mutex git__mwindow_mutex; #define MAX_SHUTDOWN_CB 8 -#ifdef GIT_OPENSSL -# include -SSL_CTX *git__ssl_ctx; -# ifdef GIT_THREADS -static git_mutex *openssl_locks; -# endif -#endif - static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; static git_atomic git__n_shutdown_callbacks; static git_atomic git__n_inits; +char *git__user_agent; void git__on_shutdown(git_global_shutdown_fn callback) { @@ -45,118 +44,49 @@ static void git__global_state_cleanup(git_global_st *st) st->error_t.message = NULL; } -static void git__shutdown(void) +static int init_common(void) +{ + int ret; + + /* Initialize the CRT debug allocator first, before our first malloc */ +#if defined(GIT_MSVC_CRTDBG) + git_win32__crtdbg_stacktrace_init(); + git_win32__stack_init(); +#endif + + /* Initialize any other subsystems that have global state */ + if ((ret = git_hash_global_init()) == 0 && + (ret = git_sysdir_global_init()) == 0 && + (ret = git_filter_global_init()) == 0 && + (ret = git_transport_ssh_global_init()) == 0) + ret = git_openssl_stream_global_init(); + + GIT_MEMORY_BARRIER; + + return ret; +} + +static void shutdown_common(void) { int pos; /* Shutdown subsystems that have registered */ - for (pos = git_atomic_get(&git__n_shutdown_callbacks); pos > 0; pos = git_atomic_dec(&git__n_shutdown_callbacks)) { - git_global_shutdown_fn cb = git__swap(git__shutdown_callbacks[pos - 1], NULL); + for (pos = git_atomic_get(&git__n_shutdown_callbacks); + pos > 0; + pos = git_atomic_dec(&git__n_shutdown_callbacks)) { + + git_global_shutdown_fn cb = git__swap( + git__shutdown_callbacks[pos - 1], NULL); + if (cb != NULL) cb(); } -} -#if defined(GIT_THREADS) && defined(GIT_OPENSSL) -void openssl_locking_function(int mode, int n, const char *file, int line) -{ - int lock; + git__free(git__user_agent); - GIT_UNUSED(file); - GIT_UNUSED(line); - - lock = mode & CRYPTO_LOCK; - - if (lock) { - git_mutex_lock(&openssl_locks[n]); - } else { - git_mutex_unlock(&openssl_locks[n]); - } -} - -static void shutdown_ssl_locking(void) -{ - int num_locks, i; - - num_locks = CRYPTO_num_locks(); - CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < num_locks; ++i) - git_mutex_free(openssl_locks); - git__free(openssl_locks); -} -#endif - -static void init_ssl(void) -{ -#ifdef GIT_OPENSSL - long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - - /* Older OpenSSL and MacOS OpenSSL doesn't have this */ -#ifdef SSL_OP_NO_COMPRESSION - ssl_opts |= SSL_OP_NO_COMPRESSION; -#endif - - SSL_load_error_strings(); - OpenSSL_add_ssl_algorithms(); - /* - * Load SSLv{2,3} and TLSv1 so that we can talk with servers - * which use the SSL hellos, which are often used for - * compatibility. We then disable SSL so we only allow OpenSSL - * to speak TLSv1 to perform the encryption itself. - */ - git__ssl_ctx = SSL_CTX_new(SSLv23_method()); - SSL_CTX_set_options(git__ssl_ctx, ssl_opts); - SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) { - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - } -#endif -} - -/** - * This function aims to clean-up the SSL context which - * we allocated. - */ -static void uninit_ssl(void) -{ -#ifdef GIT_OPENSSL - if (git__ssl_ctx) { - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - } -#endif -} - -int git_openssl_set_locking(void) -{ -#ifdef GIT_OPENSSL -# ifdef GIT_THREADS - int num_locks, i; - - num_locks = CRYPTO_num_locks(); - openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); - GITERR_CHECK_ALLOC(openssl_locks); - - for (i = 0; i < num_locks; i++) { - if (git_mutex_init(&openssl_locks[i]) != 0) { - giterr_set(GITERR_SSL, "failed to initialize openssl locks"); - return -1; - } - } - - CRYPTO_set_locking_callback(openssl_locking_function); - git__on_shutdown(shutdown_ssl_locking); - return 0; -# else - giterr_set(GITERR_THREAD, "libgit2 as not built with threads"); - return -1; -# endif -#else - giterr_set(GITERR_SSL, "libgit2 was not built with OpenSSL support"); - return -1; +#if defined(GIT_MSVC_CRTDBG) + git_win32__crtdbg_stacktrace_cleanup(); + git_win32__stack_cleanup(); #endif } @@ -204,14 +134,13 @@ static int synchronized_threads_init(void) int error; _tls_index = TlsAlloc(); + + win32_pthread_initialize(); + if (git_mutex_init(&git__mwindow_mutex)) return -1; - /* Initialize any other subsystems that have global state */ - if ((error = git_hash_global_init()) >= 0) - error = git_sysdir_global_init(); - - win32_pthread_initialize(); + error = init_common(); return error; } @@ -235,17 +164,6 @@ int git_libgit2_init(void) return ret; } -static void synchronized_threads_shutdown(void) -{ - /* Shut down any subsystems that have global state */ - git__shutdown(); - - git__free_tls_data(); - - TlsFree(_tls_index); - git_mutex_free(&git__mwindow_mutex); -} - int git_libgit2_shutdown(void) { int ret; @@ -254,8 +172,14 @@ int git_libgit2_shutdown(void) while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); } /* Only do work on a 1 -> 0 transition of the refcount */ - if ((ret = git_atomic_dec(&git__n_inits)) == 0) - synchronized_threads_shutdown(); + if ((ret = git_atomic_dec(&git__n_inits)) == 0) { + shutdown_common(); + + git__free_tls_data(); + + TlsFree(_tls_index); + git_mutex_free(&git__mwindow_mutex); + } /* Exit the lock */ InterlockedExchange(&_mutex, 0); @@ -265,18 +189,19 @@ int git_libgit2_shutdown(void) git_global_st *git__global_state(void) { - void *ptr; + git_global_st *ptr; assert(git_atomic_get(&git__n_inits) > 0); if ((ptr = TlsGetValue(_tls_index)) != NULL) return ptr; - ptr = git__malloc(sizeof(git_global_st)); + ptr = git__calloc(1, sizeof(git_global_st)); if (!ptr) return NULL; - memset(ptr, 0x0, sizeof(git_global_st)); + git_buf_init(&ptr->error_buf, 0); + TlsSetValue(_tls_index, ptr); return ptr; } @@ -313,25 +238,18 @@ static void init_once(void) { if ((init_error = git_mutex_init(&git__mwindow_mutex)) != 0) return; + pthread_key_create(&_tls_key, &cb__free_status); - - /* Initialize any other subsystems that have global state */ - if ((init_error = git_hash_global_init()) >= 0) - init_error = git_sysdir_global_init(); - - /* OpenSSL needs to be initialized from the main thread */ - init_ssl(); - - GIT_MEMORY_BARRIER; + init_error = init_common(); } int git_libgit2_init(void) { int ret; - pthread_once(&_once_init, init_once); ret = git_atomic_inc(&git__n_inits); + pthread_once(&_once_init, init_once); return init_error ? init_error : ret; } @@ -346,8 +264,7 @@ int git_libgit2_shutdown(void) return ret; /* Shut down any subsystems that have global state */ - git__shutdown(); - uninit_ssl(); + shutdown_common(); ptr = pthread_getspecific(_tls_key); pthread_setspecific(_tls_key, NULL); @@ -364,18 +281,18 @@ int git_libgit2_shutdown(void) git_global_st *git__global_state(void) { - void *ptr; + git_global_st *ptr; assert(git_atomic_get(&git__n_inits) > 0); if ((ptr = pthread_getspecific(_tls_key)) != NULL) return ptr; - ptr = git__malloc(sizeof(git_global_st)); + ptr = git__calloc(1, sizeof(git_global_st)); if (!ptr) return NULL; - memset(ptr, 0x0, sizeof(git_global_st)); + git_buf_init(&ptr->error_buf, 0); pthread_setspecific(_tls_key, ptr); return ptr; } @@ -386,14 +303,16 @@ static git_global_st __state; int git_libgit2_init(void) { - static int ssl_inited = 0; + int ret; - if (!ssl_inited) { - init_ssl(); - ssl_inited = 1; - } + /* Only init SSL the first time */ + if ((ret = git_atomic_inc(&git__n_inits)) != 1) + return ret; - return git_atomic_inc(&git__n_inits); + if ((ret = init_common()) < 0) + return ret; + + return 1; } int git_libgit2_shutdown(void) @@ -401,14 +320,12 @@ int git_libgit2_shutdown(void) int ret; /* Shut down any subsystems that have global state */ - if ((ret = git_atomic_dec(&git__n_inits)) != 0) - return ret; + if ((ret = git_atomic_dec(&git__n_inits)) == 0) { + shutdown_common(); + git__global_state_cleanup(&__state); + } - git__shutdown(); - git__global_state_cleanup(&__state); - uninit_ssl(); - - return 0; + return ret; } git_global_st *git__global_state(void) diff --git a/src/global.h b/src/global.h index fdad6ba89..9fdcee573 100644 --- a/src/global.h +++ b/src/global.h @@ -14,6 +14,7 @@ typedef struct { git_error *last_error; git_error error_t; + git_buf error_buf; char oid_fmt[GIT_OID_HEXSZ+1]; } git_global_st; @@ -34,4 +35,6 @@ extern void git__on_shutdown(git_global_shutdown_fn callback); extern void git__free_tls_data(void); +extern const char *git_libgit2__user_agent(void); + #endif diff --git a/src/idxmap.h b/src/idxmap.h new file mode 100644 index 000000000..4122a89fe --- /dev/null +++ b/src/idxmap.h @@ -0,0 +1,93 @@ +/* + * 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_idxmap_h__ +#define INCLUDE_idxmap_h__ + +#include +#include "common.h" +#include "git2/index.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) +__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) + +typedef khash_t(idx) git_idxmap; +typedef khash_t(idxicase) git_idxmap_icase; + +typedef khiter_t git_idxmap_iter; + +/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ +static kh_inline khint_t idxentry_hash(const git_index_entry *e) +{ + const char *s = e->path; + khint_t h = (khint_t)git__tolower(*s); + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); + return h + GIT_IDXENTRY_STAGE(e); +} + +#define idxentry_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) +#define idxentry_icase_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) + +#define GIT__USE_IDXMAP \ + __KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) + +#define GIT__USE_IDXMAP_ICASE \ + __KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) + +#define git_idxmap_alloc(hp) \ + ((*(hp) = kh_init(idx)) == NULL) ? giterr_set_oom(), -1 : 0 + +#define git_idxmap_icase_alloc(hp) \ + ((*(hp) = kh_init(idxicase)) == NULL) ? giterr_set_oom(), -1 : 0 + +#define git_idxmap_insert(h, key, val, rval) do { \ + khiter_t __pos = kh_put(idx, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_idxmap_icase_insert(h, key, val, rval) do { \ + khiter_t __pos = kh_put(idxicase, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_idxmap_lookup_index(h, k) kh_get(idx, h, k) +#define git_idxmap_icase_lookup_index(h, k) kh_get(idxicase, h, k) +#define git_idxmap_value_at(h, idx) kh_val(h, idx) +#define git_idxmap_valid_index(h, idx) (idx != kh_end(h)) +#define git_idxmap_has_data(h, idx) kh_exist(h, idx) + +#define git_idxmap_resize(h,s) kh_resize(idx, h, s) +#define git_idxmap_free(h) kh_destroy(idx, h), h = NULL +#define git_idxmap_clear(h) kh_clear(idx, h) + +#define git_idxmap_delete_at(h, id) kh_del(idx, h, id) +#define git_idxmap_icase_delete_at(h, id) kh_del(idxicase, h, id) + +#define git_idxmap_delete(h, key) do { \ + khiter_t __pos = git_idxmap_lookup_index(h, key); \ + if (git_idxmap_valid_index(h, __pos)) \ + git_idxmap_delete_at(h, __pos); } while (0) + +#define git_idxmap_icase_delete(h, key) do { \ + khiter_t __pos = git_idxmap_icase_lookup_index(h, key); \ + if (git_idxmap_valid_index(h, __pos)) \ + git_idxmap_icase_delete_at(h, __pos); } while (0) + +#define git_idxmap_begin kh_begin +#define git_idxmap_end kh_end + +#endif diff --git a/src/ignore.c b/src/ignore.c index 0031e4696..aedc1401e 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -89,18 +89,20 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match } /* - * If we're dealing with a directory (which we know via the - * strchr() check) we want to use 'dirname/' as the - * pattern so p_fnmatch() honours FNM_PATHNAME + * 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); } - if (!strchr(rule->pattern, '*')) - error = git_buf_printf(&buf, "%s/*", rule->pattern); - else - error = git_buf_puts(&buf, rule->pattern); + + error = git_buf_puts(&buf, rule->pattern); + + if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR) + error = git_buf_PUTS(&buf, "/*"); if (error < 0) goto out; diff --git a/src/index.c b/src/index.c index 73f0b3d26..b97f8091d 100644 --- a/src/index.c +++ b/src/index.c @@ -17,6 +17,8 @@ #include "pathspec.h" #include "ignore.h" #include "blob.h" +#include "idxmap.h" +#include "diff.h" #include "git2/odb.h" #include "git2/oid.h" @@ -24,6 +26,32 @@ #include "git2/config.h" #include "git2/sys/index.h" +GIT__USE_IDXMAP +GIT__USE_IDXMAP_ICASE + +#define INSERT_IN_MAP_EX(idx, map, e, err) do { \ + if ((idx)->ignore_case) \ + git_idxmap_icase_insert((khash_t(idxicase) *) (map), (e), (e), (err)); \ + else \ + git_idxmap_insert((map), (e), (e), (err)); \ + } while (0) + +#define INSERT_IN_MAP(idx, e, err) INSERT_IN_MAP_EX(idx, (idx)->entries_map, e, err) + +#define LOOKUP_IN_MAP(p, idx, k) do { \ + if ((idx)->ignore_case) \ + (p) = git_idxmap_icase_lookup_index((khash_t(idxicase) *) index->entries_map, (k)); \ + else \ + (p) = git_idxmap_lookup_index(index->entries_map, (k)); \ + } while (0) + +#define DELETE_IN_MAP(idx, e) do { \ + if ((idx)->ignore_case) \ + git_idxmap_icase_delete((khash_t(idxicase) *) (idx)->entries_map, (e)); \ + else \ + git_idxmap_delete((idx)->entries_map, (e)); \ + } while (0) + static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, unsigned int flags, git_index_matched_path_cb cb, void *payload); @@ -328,28 +356,6 @@ static unsigned int index_merge_mode( return git_index__create_mode(mode); } -static int index_sort_if_needed(git_index *index, bool need_lock) -{ - /* not truly threadsafe because between when this checks and/or - * sorts the array another thread could come in and unsort it - */ - - if (git_vector_is_sorted(&index->entries)) - return 0; - - if (need_lock && git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to lock index"); - return -1; - } - - git_vector_sort(&index->entries); - - if (need_lock) - git_mutex_unlock(&index->lock); - - return 0; -} - GIT_INLINE(int) index_find_in_entries( size_t *out, git_vector *entries, git_vector_cmp entry_srch, const char *path, size_t path_len, int stage) @@ -363,10 +369,9 @@ GIT_INLINE(int) index_find_in_entries( GIT_INLINE(int) index_find( size_t *out, git_index *index, - const char *path, size_t path_len, int stage, bool need_lock) + const char *path, size_t path_len, int stage) { - if (index_sort_if_needed(index, need_lock) < 0) - return -1; + git_vector_sort(&index->entries); return index_find_in_entries( out, &index->entries, index->entries_search, path, path_len, stage); @@ -390,7 +395,7 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) git_vector_set_cmp(&index->entries, ignore_case ? git_index_entry_icmp : git_index_entry_cmp); - index_sort_if_needed(index, true); + git_vector_sort(&index->entries); git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); git_vector_sort(&index->reuc); @@ -406,13 +411,7 @@ int git_index_open(git_index **index_out, const char *index_path) index = git__calloc(1, sizeof(git_index)); GITERR_CHECK_ALLOC(index); - if (git_mutex_init(&index->lock)) { - giterr_set(GITERR_OS, "Failed to initialize lock"); - git__free(index); - return -1; - } - - git_pool_init(&index->tree_pool, 1, 0); + git_pool_init(&index->tree_pool, 1); if (index_path != NULL) { index->index_file_path = git__strdup(index_path); @@ -425,6 +424,7 @@ int git_index_open(git_index **index_out, const char *index_path) } if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || + git_idxmap_alloc(&index->entries_map) < 0 || git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) @@ -462,13 +462,13 @@ static void index_free(git_index *index) assert(!git_atomic_get(&index->readers)); git_index_clear(index); + git_idxmap_free(index->entries_map); git_vector_free(&index->entries); git_vector_free(&index->names); git_vector_free(&index->reuc); git_vector_free(&index->deleted); git__free(index->index_file_path); - git_mutex_free(&index->lock); git__memzero(index, sizeof(*index)); git__free(index); @@ -508,6 +508,7 @@ static int index_remove_entry(git_index *index, size_t pos) if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); + DELETE_IN_MAP(index, entry); error = git_vector_remove(&index->entries, pos); if (!error) { @@ -530,11 +531,7 @@ int git_index_clear(git_index *index) index->tree = NULL; git_pool_clear(&index->tree_pool); - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } - + git_idxmap_clear(index->entries_map); while (!error && index->entries.length > 0) error = index_remove_entry(index, index->entries.length - 1); index_free_deleted(index); @@ -544,8 +541,6 @@ int git_index_clear(git_index *index) git_futils_filestamp_set(&index->stamp, NULL); - git_mutex_unlock(&index->lock); - return error; } @@ -608,14 +603,14 @@ const git_oid *git_index_checksum(git_index *index) */ static int compare_checksum(git_index *index) { - int fd, error; + int fd; ssize_t bytes_read; git_oid checksum = {{ 0 }}; if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0) return fd; - if ((error = p_lseek(fd, -20, SEEK_END)) < 0) { + if (p_lseek(fd, -20, SEEK_END) < 0) { p_close(fd); giterr_set(GITERR_OS, "failed to seek to end of file"); return -1; @@ -688,18 +683,13 @@ int git_index__changed_relative_to( return !!git_oid_cmp(&index->checksum, checksum); } -static bool is_racy_timestamp(git_time_t stamp, git_index_entry *entry) +static bool is_racy_entry(git_index *index, const git_index_entry *entry) { /* Git special-cases submodules in the check */ if (S_ISGITLINK(entry->mode)) return false; - /* If we never read the index, we can't have this race either */ - if (stamp == 0) - return false; - - /* If the timestamp is the same or newer than the index, it's racy */ - return ((int32_t) stamp) <= entry->mtime.seconds; + return git_index_entry_newer_than_index(entry, index); } /* @@ -711,9 +701,10 @@ static int truncate_racily_clean(git_index *index) size_t i; int error; git_index_entry *entry; - git_time_t ts = index->stamp.mtime; git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff; + git_diff *diff = NULL; + git_vector paths = GIT_VECTOR_INIT; + git_diff_delta *delta; /* Nothing to do if there's no repo to talk about */ if (!INDEX_OWNER(index)) @@ -725,21 +716,33 @@ static int truncate_racily_clean(git_index *index) diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH; git_vector_foreach(&index->entries, i, entry) { - if (!is_racy_timestamp(ts, entry)) - continue; - - diff_opts.pathspec.count = 1; - diff_opts.pathspec.strings = (char **) &entry->path; - - if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) - return error; - - if (git_diff_num_deltas(diff) > 0) - entry->file_size = 0; - - git_diff_free(diff); + if ((entry->flags_extended & GIT_IDXENTRY_UPTODATE) == 0 && + is_racy_entry(index, entry)) + git_vector_insert(&paths, (char *)entry->path); } + if (paths.length == 0) + goto done; + + diff_opts.pathspec.count = paths.length; + diff_opts.pathspec.strings = (char **)paths.contents; + + if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) + return error; + + git_vector_foreach(&diff->deltas, i, delta) { + entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); + + /* Ensure that we have a stage 0 for this file (ie, it's not a + * conflict), otherwise smudging it is quite pointless. + */ + if (entry) + entry->file_size = 0; + } + +done: + git_diff_free(diff); + git_vector_free(&paths); return 0; } @@ -796,53 +799,85 @@ const git_index_entry *git_index_get_byindex( git_index *index, size_t n) { assert(index); - if (index_sort_if_needed(index, true) < 0) - return NULL; + git_vector_sort(&index->entries); return git_vector_get(&index->entries, n); } const git_index_entry *git_index_get_bypath( git_index *index, const char *path, int stage) { - size_t pos; + khiter_t pos; + git_index_entry key = {{ 0 }}; assert(index); - if (index_find(&pos, index, path, 0, stage, true) < 0) { - giterr_set(GITERR_INDEX, "Index does not contain %s", path); - return NULL; - } + key.path = path; + GIT_IDXENTRY_STAGE_SET(&key, stage); - return git_index_get_byindex(index, pos); + LOOKUP_IN_MAP(pos, index, &key); + + if (git_idxmap_valid_index(index->entries_map, pos)) + return git_idxmap_value_at(index->entries_map, pos); + + giterr_set(GITERR_INDEX, "Index does not contain %s", path); + return NULL; } void git_index_entry__init_from_stat( git_index_entry *entry, struct stat *st, bool trust_mode) { - entry->ctime.seconds = (git_time_t)st->st_ctime; - entry->mtime.seconds = (git_time_t)st->st_mtime; - /* entry->mtime.nanoseconds = st->st_mtimensec; */ - /* entry->ctime.nanoseconds = st->st_ctimensec; */ + entry->ctime.seconds = (int32_t)st->st_ctime; + entry->mtime.seconds = (int32_t)st->st_mtime; +#if defined(GIT_USE_NSEC) + entry->mtime.nanoseconds = st->st_mtime_nsec; + entry->ctime.nanoseconds = st->st_ctime_nsec; +#endif entry->dev = st->st_rdev; entry->ino = st->st_ino; entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? git_index__create_mode(0666) : git_index__create_mode(st->st_mode); entry->uid = st->st_uid; entry->gid = st->st_gid; - entry->file_size = st->st_size; + entry->file_size = (uint32_t)st->st_size; } +static void index_entry_adjust_namemask( + git_index_entry *entry, + size_t path_length) +{ + entry->flags &= ~GIT_IDXENTRY_NAMEMASK; + + if (path_length < GIT_IDXENTRY_NAMEMASK) + entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK; + else + entry->flags |= GIT_IDXENTRY_NAMEMASK; +} + +/* When `from_workdir` is true, we will validate the paths to avoid placing + * paths that are invalid for the working directory on the current filesystem + * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This + * function will *always* prevent `.git` and directory traversal `../` from + * being added to the index. + */ static int index_entry_create( git_index_entry **out, git_repository *repo, - const char *path) + const char *path, + bool from_workdir) { size_t pathlen = strlen(path), alloclen; struct entry_internal *entry; + unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; - if (!git_path_isvalid(repo, path, - GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT)) { - giterr_set(GITERR_INDEX, "Invalid path: '%s'", path); + /* always reject placing `.git` in the index and directory traversal. + * when requested, disallow platform-specific filenames and upgrade to + * the platform-specific `.git` tests (eg, `git~1`, etc). + */ + if (from_workdir) + path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; + + if (!git_path_isvalid(repo, path, path_valid_flags)) { + giterr_set(GITERR_INDEX, "invalid path: '%s'", path); return -1; } @@ -874,7 +909,7 @@ static int index_entry_init( "Could not initialize index entry. " "Index is not backed up by an existing repository."); - if (index_entry_create(&entry, INDEX_OWNER(index), rel_path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, true) < 0) return -1; /* write the blob to disk and get the oid and stat info */ @@ -940,30 +975,46 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, return 0; } -static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src) +static void index_entry_cpy( + git_index_entry *tgt, + const git_index_entry *src) { const char *tgt_path = tgt->path; memcpy(tgt, src, sizeof(*tgt)); - tgt->path = tgt_path; /* reset to existing path data */ + tgt->path = tgt_path; } static int index_entry_dup( git_index_entry **out, - git_repository *repo, + git_index *index, const git_index_entry *src) { - git_index_entry *entry; - - if (!src) { - *out = NULL; - return 0; - } - - if (index_entry_create(&entry, repo, src->path) < 0) + if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) return -1; - index_entry_cpy(entry, src); - *out = entry; + index_entry_cpy(*out, src); + return 0; +} + +static void index_entry_cpy_nocache( + git_index_entry *tgt, + const git_index_entry *src) +{ + git_oid_cpy(&tgt->id, &src->id); + tgt->mode = src->mode; + tgt->flags = src->flags; + tgt->flags_extended = (src->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS); +} + +static int index_entry_dup_nocache( + git_index_entry **out, + git_index *index, + const git_index_entry *src) +{ + if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) + return -1; + + index_entry_cpy_nocache(*out, src); return 0; } @@ -1019,7 +1070,7 @@ static int has_dir_name(git_index *index, } len = slash - name; - if (!index_find(&pos, index, name, len, stage, false)) { + if (!index_find(&pos, index, name, len, stage)) { retval = -1; if (!ok_to_replace) break; @@ -1065,6 +1116,76 @@ static int check_file_directory_collision(git_index *index, return 0; } +static int canonicalize_directory_path( + git_index *index, + git_index_entry *entry, + git_index_entry *existing) +{ + const git_index_entry *match, *best = NULL; + char *search, *sep; + size_t pos, search_len, best_len; + + if (!index->ignore_case) + return 0; + + /* item already exists in the index, simply re-use the existing case */ + if (existing) { + memcpy((char *)entry->path, existing->path, strlen(existing->path)); + return 0; + } + + /* nothing to do */ + if (strchr(entry->path, '/') == NULL) + return 0; + + if ((search = git__strdup(entry->path)) == NULL) + return -1; + + /* starting at the parent directory and descending to the root, find the + * common parent directory. + */ + while (!best && (sep = strrchr(search, '/'))) { + sep[1] = '\0'; + + search_len = strlen(search); + + git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, search); + + while ((match = git_vector_get(&index->entries, pos))) { + if (GIT_IDXENTRY_STAGE(match) != 0) { + /* conflicts do not contribute to canonical paths */ + } else if (strncmp(search, match->path, search_len) == 0) { + /* prefer an exact match to the input filename */ + best = match; + best_len = search_len; + break; + } else if (strncasecmp(search, match->path, search_len) == 0) { + /* continue walking, there may be a path with an exact + * (case sensitive) match later in the index, but use this + * as the best match until that happens. + */ + if (!best) { + best = match; + best_len = search_len; + } + } else { + break; + } + + pos++; + } + + sep[0] = '\0'; + } + + if (best) + memcpy((char *)entry->path, best->path, best_len); + + git__free(search); + return 0; +} + static int index_no_dups(void **old, void *new) { const git_index_entry *entry = new; @@ -1074,18 +1195,76 @@ static int index_no_dups(void **old, void *new) return GIT_EEXISTS; } +static void index_existing_and_best( + git_index_entry **existing, + size_t *existing_position, + git_index_entry **best, + git_index *index, + const git_index_entry *entry) +{ + git_index_entry *e; + size_t pos; + int error; + + error = index_find(&pos, + index, entry->path, 0, GIT_IDXENTRY_STAGE(entry)); + + if (error == 0) { + *existing = index->entries.contents[pos]; + *existing_position = pos; + *best = index->entries.contents[pos]; + return; + } + + *existing = NULL; + *existing_position = 0; + *best = NULL; + + if (GIT_IDXENTRY_STAGE(entry) == 0) { + for (; pos < index->entries.length; pos++) { + int (*strcomp)(const char *a, const char *b) = + index->ignore_case ? git__strcasecmp : git__strcmp; + + e = index->entries.contents[pos]; + + if (strcomp(entry->path, e->path) != 0) + break; + + if (GIT_IDXENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) { + *best = e; + continue; + } else { + *best = e; + break; + } + } + } +} + /* index_insert takes ownership of the new entry - if it can't insert * it, then it will return an error **and also free the entry**. When * it replaces an existing entry, it will update the entry_ptr with the * actual entry in the index (and free the passed in one). + * + * trust_path is whether we use the given path, or whether (on case + * insensitive systems only) we try to canonicalize the given path to + * be within an existing directory. + * * trust_mode is whether we trust the mode in entry_ptr. + * + * trust_id is whether we trust the id or it should be validated. */ static int index_insert( - git_index *index, git_index_entry **entry_ptr, int replace, bool trust_mode) + git_index *index, + git_index_entry **entry_ptr, + int replace, + bool trust_path, + bool trust_mode, + bool trust_id) { int error = 0; size_t path_length, position; - git_index_entry *existing = NULL, *entry; + git_index_entry *existing, *best, *entry; assert(index && entry_ptr); @@ -1093,34 +1272,40 @@ static int index_insert( /* make sure that the path length flag is correct */ path_length = ((struct entry_internal *)entry)->pathlen; + index_entry_adjust_namemask(entry, path_length); - entry->flags &= ~GIT_IDXENTRY_NAMEMASK; - - if (path_length < GIT_IDXENTRY_NAMEMASK) - entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK; - else - entry->flags |= GIT_IDXENTRY_NAMEMASK; - - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire index lock"); - return -1; - } + /* this entry is now up-to-date and should not be checked for raciness */ + entry->flags_extended |= GIT_IDXENTRY_UPTODATE; git_vector_sort(&index->entries); - /* look if an entry with this path already exists */ - if (!index_find( - &position, index, entry->path, 0, GIT_IDXENTRY_STAGE(entry), false)) { - existing = index->entries.contents[position]; - /* update filemode to existing values if stat is not trusted */ - if (trust_mode) - entry->mode = git_index__create_mode(entry->mode); - else - entry->mode = index_merge_mode(index, existing, entry->mode); + /* look if an entry with this path already exists, either staged, or (if + * this entry is a regular staged item) as the "ours" side of a conflict. + */ + index_existing_and_best(&existing, &position, &best, index, entry); + + /* update the file mode */ + entry->mode = trust_mode ? + git_index__create_mode(entry->mode) : + index_merge_mode(index, best, entry->mode); + + /* canonicalize the directory name */ + if (!trust_path) + error = canonicalize_directory_path(index, entry, best); + + /* ensure that the given id exists (unless it's a submodule) */ + if (!error && !trust_id && INDEX_OWNER(index) && + (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) { + + if (!git_object__is_valid(INDEX_OWNER(index), &entry->id, + git_object__type_from_filemode(entry->mode))) + error = -1; } /* look for tree / blob name collisions, removing conflicts if requested */ - error = check_file_directory_collision(index, entry, position, replace); + if (!error) + error = check_file_directory_collision(index, entry, position, replace); + if (error < 0) /* skip changes */; @@ -1128,8 +1313,13 @@ static int index_insert( * and return it in place of the passed in one. */ else if (existing) { - if (replace) + if (replace) { index_entry_cpy(existing, entry); + + if (trust_path) + memcpy((char *)existing->path, entry->path, strlen(entry->path)); + } + index_entry_free(entry); *entry_ptr = entry = existing; } @@ -1139,6 +1329,10 @@ static int index_insert( * check for dups, this is actually cheaper in the long run.) */ error = git_vector_insert_sorted(&index->entries, entry, index_no_dups); + + if (error == 0) { + INSERT_IN_MAP(index, entry, error); + } } if (error < 0) { @@ -1146,8 +1340,6 @@ static int index_insert( *entry_ptr = NULL; } - git_mutex_unlock(&index->lock); - return error; } @@ -1205,7 +1397,7 @@ int git_index_add_frombuffer( return -1; } - if (index_entry_dup(&entry, INDEX_OWNER(index), source_entry) < 0) + if (index_entry_dup(&entry, index, source_entry) < 0) return -1; error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len); @@ -1217,7 +1409,7 @@ int git_index_add_frombuffer( git_oid_cpy(&entry->id, &id); entry->file_size = len; - if ((error = index_insert(index, &entry, 1, true)) < 0) + if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) return error; /* Adding implies conflict was resolved, move conflict entries to REUC */ @@ -1238,7 +1430,7 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const struct stat st; int error; - if (index_entry_create(&entry, INDEX_OWNER(index), path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), path, true) < 0) return -1; if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0) @@ -1276,7 +1468,7 @@ int git_index_add_bypath(git_index *index, const char *path) assert(index && path); if ((ret = index_entry_init(&entry, index, path)) == 0) - ret = index_insert(index, &entry, 1, false); + ret = index_insert(index, &entry, 1, false, false, true); /* If we were given a directory, let's see if it's a submodule */ if (ret < 0 && ret != GIT_EDIRECTORY) @@ -1286,13 +1478,13 @@ int git_index_add_bypath(git_index *index, const char *path) git_submodule *sm; git_error_state err; - giterr_capture(&err, ret); + giterr_state_capture(&err, ret); ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); if (ret == GIT_ENOTFOUND) - return giterr_restore(&err); + return giterr_state_restore(&err); - git__free(err.error_msg.message); + giterr_state_free(&err); /* * EEXISTS means that there is a repository at that path, but it's not known @@ -1302,7 +1494,7 @@ int git_index_add_bypath(git_index *index, const char *path) if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) return ret; - if ((ret = index_insert(index, &entry, 1, false)) < 0) + if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0) return ret; } else if (ret < 0) { return ret; @@ -1339,6 +1531,44 @@ int git_index_remove_bypath(git_index *index, const char *path) return 0; } +int git_index__fill(git_index *index, const git_vector *source_entries) +{ + const git_index_entry *source_entry = NULL; + size_t i; + int ret = 0; + + assert(index); + + if (!source_entries->length) + return 0; + + git_vector_size_hint(&index->entries, source_entries->length); + git_idxmap_resize(index->entries_map, (khint_t)(source_entries->length * 1.3)); + + git_vector_foreach(source_entries, i, source_entry) { + git_index_entry *entry = NULL; + + if ((ret = index_entry_dup(&entry, index, source_entry)) < 0) + break; + + index_entry_adjust_namemask(entry, ((struct entry_internal *)entry)->pathlen); + entry->flags_extended |= GIT_IDXENTRY_UPTODATE; + entry->mode = git_index__create_mode(entry->mode); + + if ((ret = git_vector_insert(&index->entries, entry)) < 0) + break; + + INSERT_IN_MAP(index, entry, ret); + if (ret < 0) + break; + } + + if (!ret) + git_vector_sort(&index->entries); + + return ret; +} + int git_index_add(git_index *index, const git_index_entry *source_entry) { @@ -1352,8 +1582,8 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) return -1; } - if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 || - (ret = index_insert(index, &entry, 1, true)) < 0) + if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || + (ret = index_insert(index, &entry, 1, true, true, false)) < 0) return ret; git_tree_cache_invalidate_path(index->tree, entry->path); @@ -1364,13 +1594,14 @@ int git_index_remove(git_index *index, const char *path, int stage) { int error; size_t position; + git_index_entry remove_key = {{ 0 }}; - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } + remove_key.path = path; + GIT_IDXENTRY_STAGE_SET(&remove_key, stage); - if (index_find(&position, index, path, 0, stage, false) < 0) { + DELETE_IN_MAP(index, &remove_key); + + if (index_find(&position, index, path, 0, stage) < 0) { giterr_set( GITERR_INDEX, "Index does not contain %s at stage %d", path, stage); error = GIT_ENOTFOUND; @@ -1378,7 +1609,6 @@ int git_index_remove(git_index *index, const char *path, int stage) error = index_remove_entry(index, position); } - git_mutex_unlock(&index->lock); return error; } @@ -1389,14 +1619,9 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) size_t pos; git_index_entry *entry; - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } - if (!(error = git_buf_sets(&pfx, dir)) && !(error = git_path_to_dir(&pfx))) - index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY, false); + index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY); while (!error) { entry = git_vector_get(&index->entries, pos); @@ -1413,17 +1638,33 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) /* removed entry at 'pos' so we don't need to increment */ } - git_mutex_unlock(&index->lock); git_buf_free(&pfx); return error; } +int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) +{ + int error = 0; + size_t pos; + const git_index_entry *entry; + + index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY); + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, prefix) != 0) + error = GIT_ENOTFOUND; + + if (!error && at_pos) + *at_pos = pos; + + return error; +} + int git_index__find_pos( size_t *out, git_index *index, const char *path, size_t path_len, int stage) { assert(index && path); - return index_find(out, index, path, path_len, stage, true); + return index_find(out, index, path, path_len, stage); } int git_index_find(size_t *at_pos, git_index *index, const char *path) @@ -1432,14 +1673,8 @@ int git_index_find(size_t *at_pos, git_index *index, const char *path) assert(index && path); - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } - if (git_vector_bsearch2( &pos, &index->entries, index->entries_search_path, path) < 0) { - git_mutex_unlock(&index->lock); giterr_set(GITERR_INDEX, "Index does not contain %s", path); return GIT_ENOTFOUND; } @@ -1457,7 +1692,6 @@ int git_index_find(size_t *at_pos, git_index *index, const char *path) if (at_pos) *at_pos = pos; - git_mutex_unlock(&index->lock); return 0; } @@ -1472,16 +1706,19 @@ int git_index_conflict_add(git_index *index, assert (index); - if ((ret = index_entry_dup(&entries[0], INDEX_OWNER(index), ancestor_entry)) < 0 || - (ret = index_entry_dup(&entries[1], INDEX_OWNER(index), our_entry)) < 0 || - (ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0) + if ((ancestor_entry && + (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) || + (our_entry && + (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) || + (their_entry && + (ret = index_entry_dup(&entries[2], index, their_entry)) < 0)) goto on_error; /* Validate entries */ for (i = 0; i < 3; i++) { if (entries[i] && !valid_filemode(entries[i]->mode)) { giterr_set(GITERR_INDEX, "invalid filemode for stage %d entry", - i); + i + 1); return -1; } } @@ -1508,7 +1745,7 @@ int git_index_conflict_add(git_index *index, /* Make sure stage is correct */ GIT_IDXENTRY_STAGE_SET(entries[i], i + 1); - if ((ret = index_insert(index, &entries[i], 0, true)) < 0) + if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0) goto on_error; entries[i] = NULL; /* don't free if later entry fails */ @@ -1610,11 +1847,6 @@ static int index_conflict_remove(git_index *index, const char *path) if (path != NULL && git_index_find(&pos, index, path) < 0) return GIT_ENOTFOUND; - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to lock index"); - return -1; - } - while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) { if (path != NULL && @@ -1630,8 +1862,6 @@ static int index_conflict_remove(git_index *index, const char *path) break; } - git_mutex_unlock(&index->lock); - return error; } @@ -1790,27 +2020,24 @@ size_t git_index_reuc_entrycount(git_index *index) return index->reuc.length; } +static int index_reuc_on_dup(void **old, void *new) +{ + index_entry_reuc_free(*old); + *old = new; + return GIT_EEXISTS; +} + static int index_reuc_insert( git_index *index, - git_index_reuc_entry *reuc, - int replace) + git_index_reuc_entry *reuc) { - git_index_reuc_entry **existing = NULL; - size_t position; + int res; assert(index && reuc && reuc->path != NULL); + assert(git_vector_is_sorted(&index->reuc)); - if (!git_index_reuc_find(&position, index, reuc->path)) - existing = (git_index_reuc_entry **)&index->reuc.contents[position]; - - if (!replace || !existing) - return git_vector_insert(&index->reuc, reuc); - - /* exists, replace it */ - git__free(*existing); - *existing = reuc; - - return 0; + res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup); + return res == GIT_EEXISTS ? 0 : res; } int git_index_reuc_add(git_index *index, const char *path, @@ -1825,7 +2052,7 @@ int git_index_reuc_add(git_index *index, const char *path, if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || - (error = index_reuc_insert(index, reuc, 1)) < 0) + (error = index_reuc_insert(index, reuc)) < 0) index_entry_reuc_free(reuc); return error; @@ -1845,7 +2072,7 @@ const git_index_reuc_entry *git_index_reuc_get_bypath( if (!index->reuc.length) return NULL; - git_vector_sort(&index->reuc); + assert(git_vector_is_sorted(&index->reuc)); if (git_index_reuc_find(&pos, index, path) < 0) return NULL; @@ -1857,8 +2084,8 @@ const git_index_reuc_entry *git_index_reuc_get_byindex( git_index *index, size_t n) { assert(index); + assert(git_vector_is_sorted(&index->reuc)); - git_vector_sort(&index->reuc); return git_vector_get(&index->reuc, n); } @@ -1867,7 +2094,7 @@ int git_index_reuc_remove(git_index *index, size_t position) int error; git_index_reuc_entry *reuc; - git_vector_sort(&index->reuc); + assert(git_vector_is_sorted(&index->reuc)); reuc = git_vector_get(&index->reuc, position); error = git_vector_remove(&index->reuc, position); @@ -1922,11 +2149,11 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) /* read 3 ASCII octal numbers for stage entries */ for (i = 0; i < 3; i++) { - int tmp; + int64_t tmp; - if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || + if (git__strtol64(&tmp, buffer, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || - (unsigned)tmp > UINT_MAX) { + tmp < 0) { index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); } @@ -1980,9 +2207,10 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size #define read_conflict_name(ptr) \ len = p_strnlen(buffer, size) + 1; \ - if (size < len) \ - return index_error_invalid("reading conflict name entries"); \ - \ + if (size < len) { \ + index_error_invalid("reading conflict name entries"); \ + goto out_err; \ + } \ if (len == 1) \ ptr = NULL; \ else { \ @@ -2003,7 +2231,16 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size read_conflict_name(conflict_name->theirs); if (git_vector_insert(&index->names, conflict_name) < 0) - return -1; + goto out_err; + + continue; + +out_err: + git__free(conflict_name->ancestor); + git__free(conflict_name->ours); + git__free(conflict_name->theirs); + git__free(conflict_name); + return -1; } #undef read_conflict_name @@ -2082,7 +2319,7 @@ static size_t read_entry( entry.path = (char *)path_ptr; - if (index_entry_dup(out, INDEX_OWNER(index), &entry) < 0) + if (index_entry_dup(out, index, &entry) < 0) return 0; return entry_size; @@ -2173,13 +2410,13 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) seek_forward(INDEX_HEADER_SIZE); - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire index lock"); - return -1; - } - assert(!index->entries.length); + if (index->ignore_case) + kh_resize(idxicase, (khash_t(idxicase) *) index->entries_map, header.entry_count); + else + kh_resize(idx, index->entries_map, header.entry_count); + /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { git_index_entry *entry; @@ -2196,6 +2433,14 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) goto done; } + INSERT_IN_MAP(index, entry, error); + + if (error < 0) { + index_entry_free(entry); + goto done; + } + error = 0; + seek_forward(entry_size); } @@ -2242,10 +2487,9 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) * in-memory index is supposed to be case-insensitive */ git_vector_set_sorted(&index->entries, !index->ignore_case); - error = index_sort_if_needed(index, false); + git_vector_sort(&index->entries); done: - git_mutex_unlock(&index->lock); return error; } @@ -2316,7 +2560,8 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) if (entry->flags & GIT_IDXENTRY_EXTENDED) { struct entry_long *ondisk_ext; ondisk_ext = (struct entry_long *)ondisk; - ondisk_ext->flags_extended = htons(entry->flags_extended); + ondisk_ext->flags_extended = htons(entry->flags_extended & + GIT_IDXENTRY_EXTENDED_FLAGS); path = ondisk_ext->path; } else @@ -2334,11 +2579,6 @@ static int write_entries(git_index *index, git_filebuf *file) git_vector case_sorted, *entries; git_index_entry *entry; - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } - /* If index->entries is sorted case-insensitively, then we need * to re-sort it case-sensitively before writing */ if (index->ignore_case) { @@ -2353,8 +2593,6 @@ static int write_entries(git_index *index, git_filebuf *file) if ((error = write_disk_entry(file, entry)) < 0) break; - git_mutex_unlock(&index->lock); - if (index->ignore_case) git_vector_free(&case_sorted); @@ -2499,6 +2737,15 @@ static int write_tree_extension(git_index *index, git_filebuf *file) return error; } +static void clear_uptodate(git_index *index) +{ + git_index_entry *entry; + size_t i; + + git_vector_foreach(&index->entries, i, entry) + entry->flags_extended &= ~GIT_IDXENTRY_UPTODATE; +} + static int write_index(git_oid *checksum, git_index *index, git_filebuf *file) { git_oid hash_final; @@ -2538,7 +2785,13 @@ static int write_index(git_oid *checksum, git_index *index, git_filebuf *file) git_oid_cpy(checksum, &hash_final); /* write it at the end of the file */ - return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); + if (git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ) < 0) + return -1; + + /* file entries are no longer up to date */ + clear_uptodate(index); + + return 0; } int git_index_entry_stage(const git_index_entry *entry) @@ -2573,7 +2826,7 @@ static int read_tree_cb( if (git_buf_joinpath(&path, root, tentry->filename) < 0) return -1; - if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0) + if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, false) < 0) return -1; entry->mode = tentry->attr; @@ -2591,11 +2844,7 @@ static int read_tree_cb( entry->flags_extended = 0; } - if (path.size < GIT_IDXENTRY_NAMEMASK) - entry->flags = path.size & GIT_IDXENTRY_NAMEMASK; - else - entry->flags = GIT_IDXENTRY_NAMEMASK; - + index_entry_adjust_namemask(entry, path.size); git_buf_free(&path); if (git_vector_insert(data->new_entries, entry) < 0) { @@ -2610,7 +2859,13 @@ int git_index_read_tree(git_index *index, const git_tree *tree) { int error = 0; git_vector entries = GIT_VECTOR_INIT; + git_idxmap *entries_map; read_tree_data data; + size_t i; + git_index_entry *e; + + if (git_idxmap_alloc(&entries_map) < 0) + return -1; git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ @@ -2622,26 +2877,39 @@ int git_index_read_tree(git_index *index, const git_tree *tree) index->tree = NULL; git_pool_clear(&index->tree_pool); - if (index_sort_if_needed(index, true) < 0) - return -1; + git_vector_sort(&index->entries); - error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); + if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) + goto cleanup; - if (!error) { - git_vector_sort(&entries); + if (index->ignore_case) + kh_resize(idxicase, (khash_t(idxicase) *) entries_map, entries.length); + else + kh_resize(idx, entries_map, entries.length); - if ((error = git_index_clear(index)) < 0) - /* well, this isn't good */; - else if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire index lock"); - error = -1; - } else { - git_vector_swap(&entries, &index->entries); - git_mutex_unlock(&index->lock); + git_vector_foreach(&entries, i, e) { + INSERT_IN_MAP_EX(index, entries_map, e, error); + + if (error < 0) { + giterr_set(GITERR_INDEX, "failed to insert entry into map"); + return error; } } + error = 0; + + git_vector_sort(&entries); + + if ((error = git_index_clear(index)) < 0) { + /* well, this isn't good */; + } else { + git_vector_swap(&entries, &index->entries); + entries_map = git__swap(index->entries_map, entries_map); + } + +cleanup: git_vector_free(&entries); + git_idxmap_free(entries_map); if (error < 0) return error; @@ -2656,30 +2924,42 @@ int git_index_read_index( { git_vector new_entries = GIT_VECTOR_INIT, remove_entries = GIT_VECTOR_INIT; + git_idxmap *new_entries_map = NULL; git_iterator *index_iterator = NULL; git_iterator *new_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *old_entry, *new_entry; git_index_entry *entry; size_t i; int error; if ((error = git_vector_init(&new_entries, new_index->entries.length, index->entries._cmp)) < 0 || - (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0) + (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || + (error = git_idxmap_alloc(&new_entries_map)) < 0) goto done; - if ((error = git_iterator_for_index(&index_iterator, - index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&new_iterator, - (git_index *)new_index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + if (index->ignore_case) + kh_resize(idxicase, (khash_t(idxicase) *) new_entries_map, new_index->entries.length); + else + kh_resize(idx, new_entries_map, new_index->entries.length); + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_index(&index_iterator, git_index_owner(index), index, &opts)) < 0 || + (error = git_iterator_for_index(&new_iterator, git_index_owner(new_index), (git_index *)new_index, &opts)) < 0) goto done; - if (((error = git_iterator_current(&old_entry, index_iterator)) < 0 && + if (((error = git_iterator_current(&old_entry, index_iterator)) < 0 && error != GIT_ITEROVER) || - ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && + ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && error != GIT_ITEROVER)) goto done; while (true) { + git_index_entry + *dup_entry = NULL, + *add_entry = NULL, + *remove_entry = NULL; int diff; if (old_entry && new_entry) @@ -2692,27 +2972,39 @@ int git_index_read_index( break; if (diff < 0) { - git_vector_insert(&remove_entries, (git_index_entry *)old_entry); + remove_entry = (git_index_entry *)old_entry; } else if (diff > 0) { - if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0) - goto done; - - git_vector_insert(&new_entries, entry); + dup_entry = (git_index_entry *)new_entry; } else { /* Path and stage are equal, if the OID is equal, keep it to * keep the stat cache data. */ if (git_oid_equal(&old_entry->id, &new_entry->id)) { - git_vector_insert(&new_entries, (git_index_entry *)old_entry); + add_entry = (git_index_entry *)old_entry; } else { - if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0) - goto done; - - git_vector_insert(&new_entries, entry); - git_vector_insert(&remove_entries, (git_index_entry *)old_entry); + dup_entry = (git_index_entry *)new_entry; + remove_entry = (git_index_entry *)old_entry; } } + if (dup_entry) { + if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0) + goto done; + } + + if (add_entry) { + if ((error = git_vector_insert(&new_entries, add_entry)) == 0) + INSERT_IN_MAP_EX(index, new_entries_map, add_entry, error); + } + + if (remove_entry && error >= 0) + error = git_vector_insert(&remove_entries, remove_entry); + + if (error < 0) { + giterr_set(GITERR_INDEX, "failed to insert entry"); + return error; + } + if (diff <= 0) { if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 && error != GIT_ITEROVER) @@ -2730,6 +3022,7 @@ int git_index_read_index( git_index_reuc_clear(index); git_vector_swap(&new_entries, &index->entries); + new_entries_map = git__swap(index->entries_map, new_entries_map); git_vector_foreach(&remove_entries, i, entry) { if (index->tree) @@ -2741,6 +3034,7 @@ int git_index_read_index( error = 0; done: + git_idxmap_free(new_entries_map); git_vector_free(&new_entries); git_vector_free(&remove_entries); git_iterator_free(index_iterator); @@ -3010,18 +3304,11 @@ int git_index_snapshot_new(git_vector *snap, git_index *index) GIT_REFCOUNT_INC(index); - if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Failed to lock index"); - return -1; - } - git_atomic_inc(&index->readers); git_vector_sort(&index->entries); error = git_vector_dup(snap, &index->entries, index->entries._cmp); - git_mutex_unlock(&index->lock); - if (error < 0) git_index_free(index); @@ -3034,11 +3321,6 @@ void git_index_snapshot_release(git_vector *snap, git_index *index) git_atomic_dec(&index->readers); - if (!git_mutex_lock(&index->lock)) { - index_free_deleted(index); /* try to free pending deleted items */ - git_mutex_unlock(&index->lock); - } - git_index_free(index); } @@ -3103,9 +3385,7 @@ int git_indexwriter_commit(git_indexwriter *writer) if (!writer->should_write) return 0; - if (index_sort_if_needed(writer->index, true) < 0) - return -1; - + git_vector_sort(&writer->index->entries); git_vector_sort(&writer->index->reuc); if ((error = write_index(&checksum, writer->index, &writer->file)) < 0) { diff --git a/src/index.h b/src/index.h index 9c60b015c..8b9b49498 100644 --- a/src/index.h +++ b/src/index.h @@ -10,6 +10,7 @@ #include "fileops.h" #include "filebuf.h" #include "vector.h" +#include "idxmap.h" #include "tree-cache.h" #include "git2/odb.h" #include "git2/index.h" @@ -25,8 +26,8 @@ struct git_index { git_oid checksum; /* checksum at the end of the file */ git_vector entries; + git_idxmap *entries_map; - git_mutex lock; /* lock held while entries is being changed */ git_vector deleted; /* deleted entries if readers > 0 */ git_atomic readers; /* number of active iterators */ @@ -63,6 +64,45 @@ extern int git_index_entry_icmp(const void *a, const void *b); extern int git_index_entry_srch(const void *a, const void *b); extern int git_index_entry_isrch(const void *a, const void *b); +/* Index time handling functions */ +GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two) +{ + if (one->seconds != two->seconds) + return false; + +#ifdef GIT_USE_NSEC + if (one->nanoseconds != two->nanoseconds) + return false; +#endif + + return true; +} + +/* + * Test if the given index time is newer than the given existing index entry. + * If the timestamps are exactly equivalent, then the given index time is + * considered "racily newer" than the existing index entry. + */ +GIT_INLINE(bool) git_index_entry_newer_than_index( + const git_index_entry *entry, git_index *index) +{ + /* If we never read the index, we can't have this race either */ + if (!index || index->stamp.mtime.tv_sec == 0) + return false; + + /* If the timestamp is the same or newer than the index, it's racy */ +#if defined(GIT_USE_NSEC) + if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds) + return true; + else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds) + return false; + else + return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds; +#else + return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds; +#endif +} + /* Search index for `path`, returning GIT_ENOTFOUND if it does not exist * (but not setting an error message). * @@ -72,6 +112,8 @@ extern int git_index_entry_isrch(const void *a, const void *b); extern int git_index__find_pos( size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage); +extern int git_index__fill(git_index *index, const git_vector *source_entries); + extern void git_index__set_ignore_case(git_index *index, bool ignore_case); extern unsigned int git_index__create_mode(unsigned int mode); diff --git a/src/iterator.c b/src/iterator.c index cf51a340d..024a97573 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -31,14 +31,22 @@ (P)->base.cb = &(P)->cb; \ ITERATOR_SET_CB(P,NAME_LC); \ (P)->base.repo = (REPO); \ - (P)->base.start = start ? git__strdup(start) : NULL; \ - (P)->base.end = end ? git__strdup(end) : NULL; \ - if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \ + (P)->base.start = options && options->start ? \ + git__strdup(options->start) : NULL; \ + (P)->base.end = options && options->end ? \ + git__strdup(options->end) : NULL; \ + if ((options && options->start && !(P)->base.start) || \ + (options && options->end && !(P)->base.end)) { \ git__free(P); return -1; } \ + (P)->base.strcomp = git__strcmp; \ + (P)->base.strncomp = git__strncmp; \ (P)->base.prefixcomp = git__prefixcmp; \ - (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \ + (P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \ if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \ (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \ + if (options && options->pathlist.count && \ + iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \ + git__free(P); return -1; } \ } while (0) #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) @@ -56,6 +64,139 @@ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0) +typedef enum { + ITERATOR_PATHLIST_NONE = 0, + ITERATOR_PATHLIST_MATCH = 1, + ITERATOR_PATHLIST_MATCH_DIRECTORY = 2, + ITERATOR_PATHLIST_MATCH_CHILD = 3, +} iterator_pathlist__match_t; + +static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec) +{ + size_t i; + + if (git_vector_init(&iter->pathlist, pathspec->count, + (git_vector_cmp)iter->strcomp) < 0) + return -1; + + for (i = 0; i < pathspec->count; i++) { + if (!pathspec->strings[i]) + continue; + + if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0) + return -1; + } + + git_vector_sort(&iter->pathlist); + + return 0; +} + +static iterator_pathlist__match_t iterator_pathlist__match( + git_iterator *iter, const char *path, size_t path_len) +{ + const char *p; + size_t idx; + int error; + + error = git_vector_bsearch2(&idx, &iter->pathlist, + (git_vector_cmp)iter->strcomp, path); + + if (error == 0) + return ITERATOR_PATHLIST_MATCH; + + /* at this point, the path we're examining may be a directory (though we + * don't know that yet, since we're avoiding a stat unless it's necessary) + * so see if the pathlist contains a file beneath this directory. + */ + while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { + if (iter->prefixcomp(p, path) != 0) + break; + + /* an exact match would have been matched by the bsearch above */ + assert(p[path_len]); + + /* is this a literal directory entry (eg `foo/`) or a file beneath */ + if (p[path_len] == '/') { + return (p[path_len+1] == '\0') ? + ITERATOR_PATHLIST_MATCH_DIRECTORY : + ITERATOR_PATHLIST_MATCH_CHILD; + } + + if (p[path_len] > '/') + break; + + idx++; + } + + return ITERATOR_PATHLIST_NONE; +} + +static void iterator_pathlist_walk__reset(git_iterator *iter) +{ + iter->pathlist_walk_idx = 0; +} + +/* walker for the index iterator that allows it to walk the sorted pathlist + * entries alongside the sorted index entries. the `iter->pathlist_walk_idx` + * stores the starting position for subsequent calls, the position is advanced + * along with the index iterator, with a special case for handling directories + * in the pathlist that are specified without trailing '/'. (eg, `foo`). + * we do not advance over these entries until we're certain that the index + * iterator will not ask us for a file beneath that directory (eg, `foo/bar`). + */ +static bool iterator_pathlist_walk__contains(git_iterator *iter, const char *path) +{ + size_t i; + char *p; + size_t p_len; + int cmp; + + for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { + p = iter->pathlist.contents[i]; + p_len = strlen(p); + + /* see if the pathlist entry is a prefix of this path */ + cmp = iter->strncomp(p, path, p_len); + + /* this pathlist entry sorts before the given path, try the next */ + if (!p_len || cmp < 0) + iter->pathlist_walk_idx++; + + /* this pathlist sorts after the given path, no match. */ + else if (cmp > 0) + return false; + + /* match! an exact match (`foo` vs `foo`), the path is a child of an + * explicit directory in the pathlist (`foo/` vs `foo/bar`) or the path + * is a child of an entry in the pathlist (`foo` vs `foo/bar`) + */ + else if (path[p_len] == '\0' || p[p_len - 1] == '/' || path[p_len] == '/') + return true; + + /* only advance the start index for future callers if we know that we + * will not see a child of this path. eg, a pathlist entry `foo` is + * a prefix for `foo.txt` and `foo/bar`. don't advance the start + * pathlist index when we see `foo.txt` or we would miss a subsequent + * inspection of `foo/bar`. only advance when there are no more + * potential children. + */ + else if (path[p_len] > '/') + iter->pathlist_walk_idx++; + } + + return false; +} + +static void iterator_pathlist__update_ignore_case(git_iterator *iter) +{ + git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); + git_vector_sort(&iter->pathlist); + + iter->pathlist_walk_idx = 0; +} + + static int iterator__reset_range( git_iterator *iter, const char *start, const char *end) { @@ -82,7 +223,8 @@ static int iterator__update_ignore_case( git_iterator *iter, git_iterator_flag_t flags) { - int error = 0, ignore_case = -1; + bool ignore_case; + int error; if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) ignore_case = true; @@ -91,19 +233,29 @@ static int iterator__update_ignore_case( else { git_index *index; - if (!(error = git_repository_index__weakptr(&index, iter->repo))) - ignore_case = (index->ignore_case != false); + if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) + return error; + + ignore_case = (index->ignore_case == 1); } - if (ignore_case > 0) + if (ignore_case) { iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE); - else if (ignore_case == 0) + + iter->strcomp = git__strcasecmp; + iter->strncomp = git__strncasecmp; + iter->prefixcomp = git__prefixcmp_icase; + } else { iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); - iter->prefixcomp = iterator__ignore_case(iter) ? - git__prefixcmp_icase : git__prefixcmp; + iter->strcomp = git__strcmp; + iter->strncomp = git__strncmp; + iter->prefixcomp = git__prefixcmp; + } - return error; + iterator_pathlist__update_ignore_case(iter); + + return 0; } GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry) @@ -149,9 +301,7 @@ typedef struct { int git_iterator_for_nothing( git_iterator **iter, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { empty_iterator *i = git__calloc(1, sizeof(empty_iterator)); GITERR_CHECK_ALLOC(i); @@ -162,7 +312,7 @@ int git_iterator_for_nothing( ITERATOR_BASE_INIT(i, empty, EMPTY, NULL); - if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) i->base.flags |= GIT_ITERATOR_IGNORE_CASE; *iter = (git_iterator *)i; @@ -201,7 +351,6 @@ typedef struct { int path_ambiguities; bool path_has_filename; bool entry_is_current; - int (*strncomp)(const char *a, const char *b, size_t sz); } tree_iterator; static char *tree_iterator__current_filename( @@ -271,7 +420,7 @@ static int tree_iterator__search_cmp(const void *key, const void *val, void *p) return git_path_cmp( tf->start, tf->startlen, false, te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE, - ((tree_iterator *)p)->strncomp); + ((git_iterator *)p)->strncomp); } static bool tree_iterator__move_to_next( @@ -303,7 +452,7 @@ static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf) for (; tf->next < tf->n_entries; tf->next++, last = te) { te = tf->entries[tf->next]->te; - if (last && tree_iterator__te_cmp(last, te, ti->strncomp)) + if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp)) break; /* try to load trees for items in [current,next) range */ @@ -409,6 +558,8 @@ static bool tree_iterator__pop_frame(tree_iterator *ti, bool final) { tree_iterator_frame *tf = ti->head; + assert(tf); + if (!tf->up) return false; @@ -418,7 +569,7 @@ static bool tree_iterator__pop_frame(tree_iterator *ti, bool final) tree_iterator__move_to_next(ti, tf); if (!final) { /* if final, don't bother to clean up */ - git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries); + // TODO: maybe free the pool so far? git_buf_rtruncate_at_char(&ti->path, '/'); } @@ -432,6 +583,8 @@ static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final) while (tree_iterator__pop_frame(ti, final)) /* pop to root */; if (!final) { + assert(ti->head); + ti->head->current = to_end ? ti->head->n_entries : 0; ti->path_ambiguities = 0; git_buf_clear(&ti->path); @@ -468,7 +621,7 @@ static int tree_iterator__update_entry(tree_iterator *ti) return 0; } -static int tree_iterator__current( +static int tree_iterator__current_internal( const git_index_entry **entry, git_iterator *self) { int error; @@ -491,41 +644,32 @@ static int tree_iterator__current( return 0; } -static int tree_iterator__advance_into( - const git_index_entry **entry, git_iterator *self) +static int tree_iterator__advance_into_internal(git_iterator *self) { int error = 0; tree_iterator *ti = (tree_iterator *)self; - iterator__clear_entry(entry); - if (tree_iterator__at_tree(ti)) error = tree_iterator__push_frame(ti); - if (!error && entry) - error = tree_iterator__current(entry, self); - return error; } -static int tree_iterator__advance( - const git_index_entry **entry, git_iterator *self) +static int tree_iterator__advance_internal(git_iterator *self) { int error; tree_iterator *ti = (tree_iterator *)self; tree_iterator_frame *tf = ti->head; - iterator__clear_entry(entry); - if (tf->current >= tf->n_entries) return GIT_ITEROVER; if (!iterator__has_been_accessed(ti)) - return tree_iterator__current(entry, self); + return 0; if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) && tree_iterator__at_tree(ti)) - return tree_iterator__advance_into(entry, self); + return tree_iterator__advance_into_internal(self); if (ti->path_has_filename) { git_buf_rtruncate_at_char(&ti->path, '/'); @@ -534,7 +678,7 @@ static int tree_iterator__advance( /* scan forward and up, advancing in frame or popping frame when done */ while (!tree_iterator__move_to_next(ti, tf) && - tree_iterator__pop_frame(ti, false)) + tree_iterator__pop_frame(ti, false)) tf = ti->head; /* find next and load trees */ @@ -543,7 +687,63 @@ static int tree_iterator__advance( /* deal with include_trees / auto_expand as needed */ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti)) - return tree_iterator__advance_into(entry, self); + return tree_iterator__advance_into_internal(self); + + return 0; +} + +static int tree_iterator__current( + const git_index_entry **out, git_iterator *self) +{ + const git_index_entry *entry = NULL; + iterator_pathlist__match_t m; + int error; + + do { + if ((error = tree_iterator__current_internal(&entry, self)) < 0) + return error; + + if (self->pathlist.length) { + m = iterator_pathlist__match( + self, entry->path, strlen(entry->path)); + + if (m != ITERATOR_PATHLIST_MATCH) { + if ((error = tree_iterator__advance_internal(self)) < 0) + return error; + + entry = NULL; + } + } + } while (!entry); + + if (out) + *out = entry; + + return error; +} + +static int tree_iterator__advance( + const git_index_entry **entry, git_iterator *self) +{ + int error = tree_iterator__advance_internal(self); + + iterator__clear_entry(entry); + + if (error < 0) + return error; + + return tree_iterator__current(entry, self); +} + +static int tree_iterator__advance_into( + const git_index_entry **entry, git_iterator *self) +{ + int error = tree_iterator__advance_into_internal(self); + + iterator__clear_entry(entry); + + if (error < 0) + return error; return tree_iterator__current(entry, self); } @@ -577,10 +777,12 @@ static void tree_iterator__free(git_iterator *self) { tree_iterator *ti = (tree_iterator *)self; - tree_iterator__pop_all(ti, true, false); + if (ti->head) { + tree_iterator__pop_all(ti, true, false); + git_tree_free(ti->head->entries[0]->tree); + git__free(ti->head); + } - git_tree_free(ti->head->entries[0]->tree); - git__free(ti->head); git_pool_clear(&ti->pool); git_buf_free(&ti->path); } @@ -607,15 +809,13 @@ static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree) int git_iterator_for_tree( git_iterator **iter, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error; tree_iterator *ti; if (tree == NULL) - return git_iterator_for_nothing(iter, flags, start, end); + return git_iterator_for_nothing(iter, options); if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0) return error; @@ -625,12 +825,12 @@ int git_iterator_for_tree( ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree)); - if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0) + if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0) goto fail; - ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp; - if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 || - (error = tree_iterator__create_root_frame(ti, tree)) < 0 || + git_pool_init(&ti->pool, sizeof(tree_iterator_entry)); + + if ((error = tree_iterator__create_root_frame(ti, tree)) < 0 || (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */ goto fail; @@ -650,6 +850,8 @@ typedef struct { git_vector entries; git_vector_cmp entry_srch; size_t current; + /* when limiting with a pathlist, this is the current index into it */ + size_t pathlist_idx; /* when not in autoexpand mode, use these to represent "tree" state */ git_buf partial; size_t partial_pos; @@ -669,15 +871,35 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii) return ie; } -static const git_index_entry *index_iterator__advance_over_conflicts(index_iterator *ii) +static const git_index_entry *index_iterator__advance_over_unwanted( + index_iterator *ii) { const git_index_entry *ie = index_iterator__index_entry(ii); + bool match; - if (!iterator__include_conflicts(ii)) { - while (ie && git_index_entry_is_conflict(ie)) { + while (ie) { + if (!iterator__include_conflicts(ii) && + git_index_entry_is_conflict(ie)) { ii->current++; ie = index_iterator__index_entry(ii); + continue; } + + /* if we have a pathlist, this entry's path must be in it to be + * returned. walk the pathlist in unison with the index to + * compare paths. + */ + if (ii->base.pathlist.length) { + match = iterator_pathlist_walk__contains(&ii->base, ie->path); + + if (!match) { + ii->current++; + ie = index_iterator__index_entry(ii); + continue; + } + } + + break; } return ie; @@ -706,7 +928,7 @@ static void index_iterator__next_prefix_tree(index_iterator *ii) static int index_iterator__first_prefix_tree(index_iterator *ii) { - const git_index_entry *ie = index_iterator__advance_over_conflicts(ii); + const git_index_entry *ie = index_iterator__advance_over_unwanted(ii); const char *scan, *prior, *slash; if (!ie || !iterator__include_trees(ii)) @@ -825,11 +1047,16 @@ static int index_iterator__reset( ii->current = 0; + iterator_pathlist_walk__reset(self); + + /* if we're given a start prefix, find it; if we're given a pathlist, find + * the first of those. start at the later of the two. + */ if (ii->base.start) git_index_snapshot_find( &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0); - if ((ie = index_iterator__advance_over_conflicts(ii)) == NULL) + if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL) return 0; if (git_buf_sets(&ii->partial, ie->path) < 0) @@ -859,10 +1086,9 @@ static void index_iterator__free(git_iterator *self) int git_iterator_for_index( git_iterator **iter, + git_repository *repo, git_index *index, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error = 0; index_iterator *ii = git__calloc(1, sizeof(index_iterator)); @@ -874,9 +1100,9 @@ int git_iterator_for_index( } ii->index = index; - ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index)); + ITERATOR_BASE_INIT(ii, index, INDEX, repo); - if ((error = iterator__update_ignore_case((git_iterator *)ii, flags)) < 0) { + if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) { git_iterator_free((git_iterator *)ii); return error; } @@ -916,6 +1142,7 @@ struct fs_iterator { size_t root_len; uint32_t dirload_flags; int depth; + iterator_pathlist__match_t pathlist_match; int (*enter_dir_cb)(fs_iterator *self); int (*leave_dir_cb)(fs_iterator *self); @@ -926,6 +1153,7 @@ struct fs_iterator { typedef struct { struct stat st; + iterator_pathlist__match_t pathlist_match; size_t path_len; char path[GIT_FLEX_ARRAY]; } fs_iterator_path_with_stat; @@ -1007,28 +1235,20 @@ static void fs_iterator__seek_frame_start( ff->index = 0; } -static int dirload_with_stat( - const char *dirpath, - size_t prefix_len, - unsigned int flags, - const char *start_stat, - const char *end_stat, - git_vector *contents) +static int dirload_with_stat(git_vector *contents, fs_iterator *fi) { git_path_diriter diriter = GIT_PATH_DIRITER_INIT; const char *path; - int (*strncomp)(const char *a, const char *b, size_t sz); - size_t start_len = start_stat ? strlen(start_stat) : 0; - size_t end_len = end_stat ? strlen(end_stat) : 0; + size_t start_len = fi->base.start ? strlen(fi->base.start) : 0; + size_t end_len = fi->base.end ? strlen(fi->base.end) : 0; fs_iterator_path_with_stat *ps; size_t path_len, cmp_len, ps_size; + iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH; int error; - strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? - git__strncasecmp : git__strncmp; - /* Any error here is equivalent to the dir not existing, skip over it */ - if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0) { + if ((error = git_path_diriter_init( + &diriter, fi->path.ptr, fi->dirload_flags)) < 0) { error = GIT_ENOTFOUND; goto done; } @@ -1037,18 +1257,31 @@ static int dirload_with_stat( if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) goto done; - assert(path_len > prefix_len); + assert(path_len > fi->root_len); /* remove the prefix if requested */ - path += prefix_len; - path_len -= prefix_len; + path += fi->root_len; + path_len -= fi->root_len; /* skip if before start_stat or after end_stat */ cmp_len = min(start_len, path_len); - if (cmp_len && strncomp(path, start_stat, cmp_len) < 0) + if (cmp_len && fi->base.strncomp(path, fi->base.start, cmp_len) < 0) continue; + /* skip if after end_stat */ cmp_len = min(end_len, path_len); - if (cmp_len && strncomp(path, end_stat, cmp_len) > 0) + if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0) + continue; + + /* if we have a pathlist that we're limiting to, examine this path. + * if the frame has already deemed us inside the path (eg, we're in + * `foo/bar` and the pathlist previously was detected to say `foo/`) + * then simply continue. otherwise, examine the pathlist looking for + * this path or children of this path. + */ + if (fi->base.pathlist.length && + fi->pathlist_match != ITERATOR_PATHLIST_MATCH && + fi->pathlist_match != ITERATOR_PATHLIST_MATCH_DIRECTORY && + !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len))) continue; /* Make sure to append two bytes, one for the path's null @@ -1062,6 +1295,8 @@ static int dirload_with_stat( memcpy(ps->path, path, path_len); + /* TODO: don't stat if assume unchanged for this path */ + if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) { if (error == GIT_ENOTFOUND) { /* file was removed between readdir and lstat */ @@ -1069,6 +1304,12 @@ static int dirload_with_stat( continue; } + if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) { + /* were looking for a directory, but this is a file */ + git__free(ps); + continue; + } + /* Treat the file as unreadable if we get any other error */ memset(&ps->st, 0, sizeof(ps->st)); ps->st.st_mode = GIT_FILEMODE_UNREADABLE; @@ -1085,6 +1326,11 @@ static int dirload_with_stat( continue; } + /* record whether this path was explicitly found in the path list + * or whether we're only examining it because something beneath it + * is in the path list. + */ + ps->pathlist_match = pathlist_match; git_vector_insert(contents, ps); } @@ -1114,13 +1360,11 @@ static int fs_iterator__expand_dir(fs_iterator *fi) ff = fs_iterator__alloc_frame(fi); GITERR_CHECK_ALLOC(ff); - error = dirload_with_stat( - fi->path.ptr, fi->root_len, fi->dirload_flags, - fi->base.start, fi->base.end, &ff->entries); + error = dirload_with_stat(&ff->entries, fi); if (error < 0) { git_error_state last_error = { 0 }; - giterr_capture(&last_error, error); + giterr_state_capture(&last_error, error); /* these callbacks may clear the error message */ fs_iterator__free_frame(ff); @@ -1128,7 +1372,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi) /* next time return value we skipped to */ fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - return giterr_restore(&last_error); + return giterr_state_restore(&last_error); } if (ff->entries.length == 0) { @@ -1196,19 +1440,14 @@ static int fs_iterator__advance_into( return error; } -static int fs_iterator__advance_over( - const git_index_entry **entry, git_iterator *self) +static void fs_iterator__advance_over_internal(git_iterator *self) { - int error = 0; fs_iterator *fi = (fs_iterator *)self; fs_iterator_frame *ff; fs_iterator_path_with_stat *next; - if (entry != NULL) - *entry = NULL; - while (fi->entry.path != NULL) { - ff = fi->stack; + ff = fi->stack; next = git_vector_get(&ff->entries, ++ff->index); if (next != NULL) @@ -1216,8 +1455,19 @@ static int fs_iterator__advance_over( fs_iterator__pop_frame(fi, ff, false); } +} - error = fs_iterator__update_entry(fi); +static int fs_iterator__advance_over( + const git_index_entry **entry, git_iterator *self) +{ + int error; + + if (entry != NULL) + *entry = NULL; + + fs_iterator__advance_over_internal(self); + + error = fs_iterator__update_entry((fs_iterator *)self); if (!error && entry != NULL) error = fs_iterator__current(entry, self); @@ -1294,40 +1544,50 @@ static int fs_iterator__update_entry(fs_iterator *fi) { fs_iterator_path_with_stat *ps; - memset(&fi->entry, 0, sizeof(fi->entry)); + while (true) { + memset(&fi->entry, 0, sizeof(fi->entry)); - if (!fi->stack) - return GIT_ITEROVER; + if (!fi->stack) + return GIT_ITEROVER; - ps = git_vector_get(&fi->stack->entries, fi->stack->index); - if (!ps) - return GIT_ITEROVER; + ps = git_vector_get(&fi->stack->entries, fi->stack->index); + if (!ps) + return GIT_ITEROVER; - git_buf_truncate(&fi->path, fi->root_len); - if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0) - return -1; + git_buf_truncate(&fi->path, fi->root_len); + if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0) + return -1; - if (iterator__past_end(fi, fi->path.ptr + fi->root_len)) - return GIT_ITEROVER; + if (iterator__past_end(fi, fi->path.ptr + fi->root_len)) + return GIT_ITEROVER; - fi->entry.path = ps->path; - git_index_entry__init_from_stat(&fi->entry, &ps->st, true); + fi->entry.path = ps->path; + fi->pathlist_match = ps->pathlist_match; + git_index_entry__init_from_stat(&fi->entry, &ps->st, true); - /* need different mode here to keep directories during iteration */ - fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); + /* need different mode here to keep directories during iteration */ + fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); - /* allow wrapper to check/update the entry (can force skip) */ - if (fi->update_entry_cb && - fi->update_entry_cb(fi) == GIT_ENOTFOUND) - return fs_iterator__advance_over(NULL, (git_iterator *)fi); + /* allow wrapper to check/update the entry (can force skip) */ + if (fi->update_entry_cb && + fi->update_entry_cb(fi) == GIT_ENOTFOUND) { + fs_iterator__advance_over_internal(&fi->base); + continue; + } - /* if this is a tree and trees aren't included, then skip */ - if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) { - int error = fs_iterator__advance_into(NULL, (git_iterator *)fi); - if (error != GIT_ENOTFOUND) - return error; - giterr_clear(); - return fs_iterator__advance_over(NULL, (git_iterator *)fi); + /* if this is a tree and trees aren't included, then skip */ + if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) { + int error = fs_iterator__advance_into(NULL, &fi->base); + + if (error != GIT_ENOTFOUND) + return error; + + giterr_clear(); + fs_iterator__advance_over_internal(&fi->base); + continue; + } + + break; } return 0; @@ -1343,6 +1603,7 @@ static int fs_iterator__initialize( return -1; } fi->root_len = fi->path.size; + fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD; fi->dirload_flags = (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) | @@ -1366,16 +1627,14 @@ static int fs_iterator__initialize( int git_iterator_for_filesystem( git_iterator **out, const char *root, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { fs_iterator *fi = git__calloc(1, sizeof(fs_iterator)); GITERR_CHECK_ALLOC(fi); ITERATOR_BASE_INIT(fi, fs, FS, NULL); - if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) fi->base.flags |= GIT_ITERATOR_IGNORE_CASE; return fs_iterator__initialize(out, fi, root); @@ -1559,9 +1818,7 @@ int git_iterator_for_workdir_ext( const char *repo_workdir, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error, precompose = 0; workdir_iterator *wi; @@ -1583,7 +1840,7 @@ int git_iterator_for_workdir_ext( wi->fi.leave_dir_cb = workdir_iterator__leave_dir; wi->fi.update_entry_cb = workdir_iterator__update_entry; - if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 || + if ((error = iterator__update_ignore_case((git_iterator *)wi, options ? options->flags : 0)) < 0 || (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0) { git_iterator_free((git_iterator *)wi); @@ -1618,6 +1875,7 @@ void git_iterator_free(git_iterator *iter) iter->cb->free(iter); + git_vector_free(&iter->pathlist); git__free(iter->start); git__free(iter->end); @@ -1687,7 +1945,7 @@ int git_iterator_current_parent_tree( if (!(tf = tf->down) || tf->current >= tf->n_entries || !(te = tf->entries[tf->current]->te) || - ti->strncomp(scan, te->filename, te->filename_len) != 0) + ti->base.strncomp(scan, te->filename, te->filename_len) != 0) return 0; scan += te->filename_len; @@ -1820,9 +2078,18 @@ int git_iterator_advance_over_with_status( if (!error) continue; + else if (error == GIT_ENOTFOUND) { + /* we entered this directory only hoping to find child matches to + * our pathlist (eg, this is `foo` and we had a pathlist entry for + * `foo/bar`). it should not be ignored, it should be excluded. + */ + if (wi->fi.pathlist_match == ITERATOR_PATHLIST_MATCH_CHILD) + *status = GIT_ITERATOR_STATUS_FILTERED; + else + wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ + error = 0; - wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ } else break; /* real error, stop here */ } else { diff --git a/src/iterator.h b/src/iterator.h index 893e5db50..ac17d2970 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -38,6 +38,21 @@ typedef enum { GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5), } git_iterator_flag_t; +typedef struct { + const char *start; + const char *end; + + /* paths to include in the iterator (literal). if set, any paths not + * listed here will be excluded from iteration. + */ + git_strarray pathlist; + + /* flags, from above */ + unsigned int flags; +} git_iterator_options; + +#define GIT_ITERATOR_OPTIONS_INIT {0} + typedef struct { int (*current)(const git_index_entry **, git_iterator *); int (*advance)(const git_index_entry **, git_iterator *); @@ -54,6 +69,10 @@ struct git_iterator { git_repository *repo; char *start; char *end; + git_vector pathlist; + size_t pathlist_walk_idx; + int (*strcomp)(const char *a, const char *b); + int (*strncomp)(const char *a, const char *b, size_t n); int (*prefixcomp)(const char *str, const char *prefix); size_t stat_calls; unsigned int flags; @@ -61,9 +80,7 @@ struct git_iterator { extern int git_iterator_for_nothing( git_iterator **out, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* tree iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value @@ -71,19 +88,16 @@ extern int git_iterator_for_nothing( extern int git_iterator_for_tree( git_iterator **out, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* index iterators will take the ignore_case value from the index; the * ignore_case flags are not used */ extern int git_iterator_for_index( git_iterator **out, + git_repository *repo, git_index *index, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); extern int git_iterator_for_workdir_ext( git_iterator **out, @@ -91,9 +105,7 @@ extern int git_iterator_for_workdir_ext( const char *repo_workdir, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* workdir iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value @@ -103,11 +115,9 @@ GIT_INLINE(int) git_iterator_for_workdir( git_repository *repo, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { - return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, flags, start, end); + return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); } /* for filesystem iterators, you have to explicitly pass in the ignore_case @@ -116,9 +126,7 @@ GIT_INLINE(int) git_iterator_for_workdir( extern int git_iterator_for_filesystem( git_iterator **out, const char *root, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); extern void git_iterator_free(git_iterator *iter); @@ -271,7 +279,8 @@ extern git_index *git_iterator_get_index(git_iterator *iter); typedef enum { GIT_ITERATOR_STATUS_NORMAL = 0, GIT_ITERATOR_STATUS_IGNORED = 1, - GIT_ITERATOR_STATUS_EMPTY = 2 + GIT_ITERATOR_STATUS_EMPTY = 2, + GIT_ITERATOR_STATUS_FILTERED = 3 } git_iterator_status_t; /* Advance over a directory and check if it contains no files or just diff --git a/src/merge.c b/src/merge.c index 863ac8f2d..d2f92ccce 100644 --- a/src/merge.c +++ b/src/merge.c @@ -20,7 +20,6 @@ #include "diff.h" #include "checkout.h" #include "tree.h" -#include "merge_file.h" #include "blob.h" #include "oid.h" #include "index.h" @@ -28,6 +27,8 @@ #include "config.h" #include "oidarray.h" #include "annotated_commit.h" +#include "commit.h" +#include "oidarray.h" #include "git2/types.h" #include "git2/repository.h" @@ -48,6 +49,19 @@ #define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) + +/** Internal merge flags. */ +enum { + /** The merge is for a virtual base in a recursive merge. */ + GIT_MERGE__VIRTUAL_BASE = (1 << 31), +}; + +enum { + /** Accept the conflict file, staging it as the merge result. */ + GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, +}; + + typedef enum { TREE_IDX_ANCESTOR = 0, TREE_IDX_OURS = 1, @@ -61,12 +75,6 @@ struct merge_diff_df_data { git_merge_diff *prev_conflict; }; -GIT_INLINE(int) merge_diff_detect_binary( - bool *binary_out, - git_repository *repo, - const git_merge_diff *conflict); - - /* Merge base computation */ int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) @@ -309,32 +317,61 @@ static int interesting(git_pqueue *list) return 0; } -int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos) +static void clear_commit_marks_1(git_commit_list **plist, + git_commit_list_node *commit, unsigned int mark) { + while (commit) { + unsigned int i; + + if (!(mark & commit->flags)) + return; + + commit->flags &= ~mark; + + for (i = 1; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + git_commit_list_insert(p, plist); + } + + commit = commit->out_degree ? commit->parents[0] : NULL; + } +} + +static void clear_commit_marks_many(git_vector *commits, unsigned int mark) +{ + git_commit_list *list = NULL; + git_commit_list_node *c; + unsigned int i; + + git_vector_foreach(commits, i, c) { + git_commit_list_insert(c, &list); + } + + while (list) + clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); +} + +static void clear_commit_marks(git_commit_list_node *commit, unsigned int mark) +{ + git_commit_list *list = NULL; + git_commit_list_insert(commit, &list); + while (list) + clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); +} + +static int paint_down_to_common( + git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos) +{ + git_pqueue list; + git_commit_list *result = NULL; + git_commit_list_node *two; + int error; unsigned int i; - git_commit_list_node *two; - git_commit_list *result = NULL, *tmp = NULL; - git_pqueue list; - - /* If there's only the one commit, there can be no merge bases */ - if (twos->length == 0) { - *out = NULL; - return 0; - } - - /* if the commit is repeated, we have a our merge base already */ - git_vector_foreach(twos, i, two) { - if (one == two) - return git_commit_list_insert(one, out) ? 0 : -1; - } if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_time_cmp) < 0) return -1; - if (git_commit_list_parse(walk, one) < 0) - return -1; - one->flags |= PARENT1; if (git_pqueue_insert(&list, one) < 0) return -1; @@ -383,19 +420,138 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l } git_pqueue_free(&list); + *out = result; + return 0; +} + +static int remove_redundant(git_revwalk *walk, git_vector *commits) +{ + git_vector work = GIT_VECTOR_INIT; + unsigned char *redundant; + unsigned int *filled_index; + unsigned int i, j; + int error = 0; + + redundant = git__calloc(commits->length, 1); + GITERR_CHECK_ALLOC(redundant); + filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); + GITERR_CHECK_ALLOC(filled_index); + + for (i = 0; i < commits->length; ++i) { + if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) + goto done; + } + + for (i = 0; i < commits->length; ++i) { + git_commit_list *common = NULL; + git_commit_list_node *commit = commits->contents[i]; + + if (redundant[i]) + continue; + + git_vector_clear(&work); + + for (j = 0; j < commits->length; j++) { + if (i == j || redundant[j]) + continue; + + filled_index[work.length] = j; + if ((error = git_vector_insert(&work, commits->contents[j])) < 0) + goto done; + } + + error = paint_down_to_common(&common, walk, commit, &work); + if (error < 0) + goto done; + + if (commit->flags & PARENT2) + redundant[i] = 1; + + for (j = 0; j < work.length; j++) { + git_commit_list_node *w = work.contents[j]; + if (w->flags & PARENT1) + redundant[filled_index[j]] = 1; + } + + clear_commit_marks(commit, ALL_FLAGS); + clear_commit_marks_many(&work, ALL_FLAGS); + + git_commit_list_free(&common); + } + + for (i = 0; i < commits->length; ++i) { + if (redundant[i]) + commits->contents[i] = NULL; + } + +done: + git__free(redundant); + git__free(filled_index); + git_vector_free(&work); + return error; +} + +int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos) +{ + int error; + unsigned int i; + git_commit_list_node *two; + git_commit_list *result = NULL, *tmp = NULL; + + /* If there's only the one commit, there can be no merge bases */ + if (twos->length == 0) { + *out = NULL; + return 0; + } + + /* if the commit is repeated, we have a our merge base already */ + git_vector_foreach(twos, i, two) { + if (one == two) + return git_commit_list_insert(one, out) ? 0 : -1; + } + + if (git_commit_list_parse(walk, one) < 0) + return -1; + + error = paint_down_to_common(&result, walk, one, twos); + if (error < 0) + return error; /* filter out any stale commits in the results */ tmp = result; result = NULL; while (tmp) { - struct git_commit_list *next = tmp->next; - if (!(tmp->item->flags & STALE)) - if (git_commit_list_insert_by_date(tmp->item, &result) == NULL) + git_commit_list_node *c = git_commit_list_pop(&tmp); + if (!(c->flags & STALE)) + if (git_commit_list_insert_by_date(c, &result) == NULL) return -1; + } - git__free(tmp); - tmp = next; + /* + * more than one merge base -- see if there are redundant merge + * bases and remove them + */ + if (result && result->next) { + git_vector redundant = GIT_VECTOR_INIT; + + while (result) + git_vector_insert(&redundant, git_commit_list_pop(&result)); + + clear_commit_marks(one, ALL_FLAGS); + clear_commit_marks_many(twos, ALL_FLAGS); + + if ((error = remove_redundant(walk, &redundant)) < 0) { + git_vector_free(&redundant); + return error; + } + + git_vector_foreach(&redundant, i, two) { + if (two != NULL) + git_commit_list_insert_by_date(two, &result); + } + + git_vector_free(&redundant); } *out = result; @@ -658,17 +814,14 @@ static int merge_conflict_resolve_automerge( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict, - unsigned int merge_file_favor, - unsigned int file_flags) + const git_merge_file_options *file_opts) { const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_file_result result = {0}; git_index_entry *index_entry; git_odb *odb = NULL; git_oid automerge_oid; int error = 0; - bool binary = false; assert(resolved && diff_list && conflict); @@ -703,12 +856,6 @@ static int merge_conflict_resolve_automerge( strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) return 0; - /* Reject binary conflicts */ - if ((error = merge_diff_detect_binary(&binary, diff_list->repo, conflict)) < 0) - return error; - if (binary) - return 0; - ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? &conflict->ancestor_entry : NULL; ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? @@ -716,16 +863,13 @@ static int merge_conflict_resolve_automerge( theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; - opts.favor = merge_file_favor; - opts.flags = file_flags; - if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 || - (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, &opts)) < 0 || - !result.automergeable || + (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, file_opts)) < 0 || + (!result.automergeable && !(file_opts->flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) || (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0) goto done; - if ((index_entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) + if ((index_entry = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry))) == NULL) GITERR_CHECK_ALLOC(index_entry); index_entry->path = git_pool_strdup(&diff_list->pool, result.path); @@ -751,8 +895,7 @@ static int merge_conflict_resolve( int *out, git_merge_diff_list *diff_list, const git_merge_diff *conflict, - unsigned int merge_file_favor, - unsigned int file_flags) + const git_merge_file_options *file_opts) { int resolved = 0; int error = 0; @@ -768,8 +911,7 @@ static int merge_conflict_resolve( if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, - merge_file_favor, file_flags)) < 0) + if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, file_opts)) < 0) goto done; *out = resolved; @@ -1162,7 +1304,7 @@ int git_merge_diff_list__find_renames( assert(diff_list && opts); - if ((opts->tree_flags & GIT_MERGE_TREE_FIND_RENAMES) == 0) + if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0) return 0; similarity_ours = git__calloc(diff_list->conflicts.length, @@ -1314,48 +1456,6 @@ GIT_INLINE(int) merge_diff_detect_type( return 0; } -GIT_INLINE(int) merge_diff_detect_binary( - bool *binary_out, - git_repository *repo, - const git_merge_diff *conflict) -{ - git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL; - int error = 0; - bool binary = false; - - if (GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->ancestor_entry)) { - if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor_entry.id)) < 0) - goto done; - - binary = git_blob_is_binary(ancestor_blob); - } - - if (!binary && - GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->our_entry)) { - if ((error = git_blob_lookup(&our_blob, repo, &conflict->our_entry.id)) < 0) - goto done; - - binary = git_blob_is_binary(our_blob); - } - - if (!binary && - GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->their_entry)) { - if ((error = git_blob_lookup(&their_blob, repo, &conflict->their_entry.id)) < 0) - goto done; - - binary = git_blob_is_binary(their_blob); - } - - *binary_out = binary; - -done: - git_blob_free(ancestor_blob); - git_blob_free(our_blob); - git_blob_free(their_blob); - - return error; -} - GIT_INLINE(int) index_entry_dup_pool( git_index_entry *out, git_pool *pool, @@ -1363,7 +1463,6 @@ GIT_INLINE(int) index_entry_dup_pool( { if (src != NULL) { memcpy(out, src, sizeof(git_index_entry)); - if ((out->path = git_pool_strdup(pool, src->path)) == NULL) return -1; } @@ -1399,7 +1498,7 @@ static git_merge_diff *merge_diff_from_index_entries( git_merge_diff *conflict; git_pool *pool = &diff_list->pool; - if ((conflict = git_pool_malloc(pool, sizeof(git_merge_diff))) == NULL) + if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) return NULL; if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || @@ -1498,10 +1597,11 @@ git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) diff_list->repo = repo; + git_pool_init(&diff_list->pool, 1); + if (git_vector_init(&diff_list->staged, 0, NULL) < 0 || git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || - git_vector_init(&diff_list->resolved, 0, NULL) < 0 || - git_pool_init(&diff_list->pool, 1, 0) < 0) { + git_vector_init(&diff_list->resolved, 0, NULL) < 0) { git_merge_diff_list__free(diff_list); return NULL; } @@ -1540,8 +1640,8 @@ static int merge_normalize_opts( git_merge_options init = GIT_MERGE_OPTIONS_INIT; memcpy(opts, &init, sizeof(init)); - opts->tree_flags = GIT_MERGE_TREE_FIND_RENAMES; - opts->rename_threshold = GIT_MERGE_TREE_RENAME_THRESHOLD; + opts->flags = GIT_MERGE_FIND_RENAMES; + opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; } if (!opts->target_limit) { @@ -1551,7 +1651,7 @@ static int merge_normalize_opts( limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); opts->target_limit = (limit <= 0) ? - GIT_MERGE_TREE_TARGET_LIMIT : (unsigned int)limit; + GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; } /* assign the internal metric with whitespace flag as payload */ @@ -1597,11 +1697,48 @@ static int merge_index_insert_reuc( mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); } -int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list) +static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) +{ + int error; + size_t i; + git_merge_diff *conflict; + + /* Add each entry in the resolved conflict to the REUC independently, since + * the paths may differ due to renames. */ + git_vector_foreach(&diff_list->resolved, i, conflict) { + const git_index_entry *ancestor = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + + const git_index_entry *ours = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + + const git_index_entry *theirs = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (ancestor != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) + return error; + + if (ours != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) + return error; + + if (theirs != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) + return error; + } + + return 0; +} + +static int index_from_diff_list(git_index **out, + git_merge_diff_list *diff_list, bool skip_reuc) { git_index *index; size_t i; - git_index_entry *entry; git_merge_diff *conflict; int error = 0; @@ -1610,10 +1747,8 @@ int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list) if ((error = git_index_new(&index)) < 0) return error; - git_vector_foreach(&diff_list->staged, i, entry) { - if ((error = git_index_add(index, entry)) < 0) - goto on_error; - } + if ((error = git_index__fill(index, &diff_list->staged)) < 0) + goto on_error; git_vector_foreach(&diff_list->conflicts, i, conflict) { const git_index_entry *ancestor = @@ -1656,31 +1791,8 @@ int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list) } } - /* Add each entry in the resolved conflict to the REUC independently, since - * the paths may differ due to renames. */ - git_vector_foreach(&diff_list->resolved, i, conflict) { - const git_index_entry *ancestor = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - - const git_index_entry *ours = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - - const git_index_entry *theirs = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if (ancestor != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) - goto on_error; - - if (ours != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) - goto on_error; - - if (theirs != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) + if (!skip_reuc) { + if ((error = index_update_reuc(index, diff_list)) < 0) goto on_error; } @@ -1689,16 +1801,19 @@ int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list) on_error: git_index_free(index); - return error; } static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) { + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + if (given) return given; - if (git_iterator_for_nothing(empty, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL) < 0) + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (git_iterator_for_nothing(empty, &opts) < 0) return NULL; return *empty; @@ -1717,6 +1832,7 @@ int git_merge__iterators( *empty_theirs = NULL; git_merge_diff_list *diff_list; git_merge_options opts; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_diff *conflict; git_vector changes; size_t i; @@ -1732,6 +1848,17 @@ int git_merge__iterators( if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) return error; + file_opts.favor = opts.file_favor; + file_opts.flags = opts.file_flags; + + /* use the git-inspired labels when virtual base building */ + if (opts.flags & GIT_MERGE__VIRTUAL_BASE) { + file_opts.ancestor_label = "merged common ancestors"; + file_opts.our_label = "Temporary merge branch 1"; + file_opts.their_label = "Temporary merge branch 2"; + file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED; + } + diff_list = git_merge_diff_list__alloc(repo); GITERR_CHECK_ALLOC(diff_list); @@ -1750,19 +1877,28 @@ int git_merge__iterators( git_vector_foreach(&changes, i, conflict) { int resolved = 0; - if ((error = merge_conflict_resolve(&resolved, diff_list, conflict, opts.file_favor, opts.file_flags)) < 0) + if ((error = merge_conflict_resolve( + &resolved, diff_list, conflict, &file_opts)) < 0) goto done; - if (!resolved) + if (!resolved) { + if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { + giterr_set(GITERR_MERGE, "merge conflicts exist"); + error = GIT_EMERGECONFLICT; + goto done; + } + git_vector_insert(&diff_list->conflicts, conflict); + } } + error = index_from_diff_list(out, diff_list, + (opts.flags & GIT_MERGE_SKIP_REUC)); + +done: if (!given_opts || !given_opts->metric) git__free(opts.metric); - error = index_from_diff_list(out, diff_list); - -done: git_merge_diff_list__free(diff_list); git_iterator_free(empty_ancestor); git_iterator_free(empty_ours); @@ -1780,14 +1916,17 @@ int git_merge_trees( const git_merge_options *merge_opts) { git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor_iter, (git_tree *)ancestor_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&our_iter, (git_tree *)our_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&their_iter, (git_tree *)their_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree( + &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) goto done; error = git_merge__iterators( @@ -1801,6 +1940,206 @@ done: return error; } +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *our_commit, + git_annotated_commit *their_commit, + size_t recursion_level, + const git_merge_options *opts); + +GIT_INLINE(int) insert_head_ids( + git_array_oid_t *ids, + const git_annotated_commit *annotated_commit) +{ + git_oid *id; + size_t i; + + if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { + id = git_array_alloc(*ids); + GITERR_CHECK_ALLOC(id); + + git_oid_cpy(id, git_commit_id(annotated_commit->commit)); + } else { + for (i = 0; i < annotated_commit->parents.size; i++) { + id = git_array_alloc(*ids); + GITERR_CHECK_ALLOC(id); + + git_oid_cpy(id, &annotated_commit->parents.ptr[i]); + } + } + + return 0; +} + +static int create_virtual_base( + git_annotated_commit **out, + git_repository *repo, + git_annotated_commit *one, + git_annotated_commit *two, + const git_merge_options *opts, + size_t recursion_level) +{ + git_annotated_commit *result = NULL; + git_index *index = NULL; + git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; + + /* Conflicts in the merge base creation do not propagate to conflicts + * in the result; the conflicted base will act as the common ancestor. + */ + if (opts) + memcpy(&virtual_opts, opts, sizeof(git_merge_options)); + + virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; + virtual_opts.flags |= GIT_MERGE__VIRTUAL_BASE; + + if ((merge_annotated_commits(&index, NULL, repo, one, two, + recursion_level + 1, &virtual_opts)) < 0) + return -1; + + result = git__calloc(1, sizeof(git_annotated_commit)); + GITERR_CHECK_ALLOC(result); + result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; + result->index = index; + + insert_head_ids(&result->parents, one); + insert_head_ids(&result->parents, two); + + *out = result; + return 0; +} + +static int compute_base( + git_annotated_commit **out, + git_repository *repo, + const git_annotated_commit *one, + const git_annotated_commit *two, + const git_merge_options *given_opts, + size_t recursion_level) +{ + git_array_oid_t head_ids = GIT_ARRAY_INIT; + 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; + int error; + + *out = NULL; + + 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) + 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)) + goto done; + + for (i = 1; i < bases.count; i++) { + recursion_level++; + + if (opts.recursion_limit && recursion_level > opts.recursion_limit) + break; + + if ((error = git_annotated_commit_lookup(&other, repo, + &bases.ids[i])) < 0 || + (error = create_virtual_base(&new_base, repo, base, other, &opts, + recursion_level)) < 0) + goto done; + + git_annotated_commit_free(base); + git_annotated_commit_free(other); + + base = new_base; + new_base = NULL; + other = NULL; + } + +done: + if (error == 0) + *out = base; + else + git_annotated_commit_free(base); + + git_annotated_commit_free(other); + git_annotated_commit_free(new_base); + git_oidarray_free(&bases); + git_array_clear(head_ids); + return error; +} + +static int iterator_for_annotated_commit( + git_iterator **out, + git_annotated_commit *commit) +{ + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (commit == NULL) { + error = git_iterator_for_nothing(out, &opts); + } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { + error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); + } else { + if (!commit->tree && + (error = git_commit_tree(&commit->tree, commit->commit)) < 0) + goto done; + + error = git_iterator_for_tree(out, commit->tree, &opts); + } + +done: + return error; +} + +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *ours, + git_annotated_commit *theirs, + size_t recursion_level, + const git_merge_options *opts) +{ + git_annotated_commit *base = NULL; + git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; + int error; + + if ((error = compute_base(&base, repo, ours, theirs, opts, + recursion_level)) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + giterr_clear(); + } + + if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || + (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || + (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || + (error = git_merge__iterators(index_out, repo, base_iter, our_iter, + their_iter, opts)) < 0) + goto done; + + if (base_out) { + *base_out = base; + base = NULL; + } + +done: + git_annotated_commit_free(base); + git_iterator_free(base_iter); + git_iterator_free(our_iter); + git_iterator_free(their_iter); + return error; +} + int git_merge_commits( git_index **out, @@ -1809,30 +2148,19 @@ int git_merge_commits( const git_commit *their_commit, const git_merge_options *opts) { - git_oid ancestor_oid; - git_commit *ancestor_commit = NULL; - git_tree *our_tree = NULL, *their_tree = NULL, *ancestor_tree = NULL; + git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; int error = 0; - if ((error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))) < 0 && - error == GIT_ENOTFOUND) - giterr_clear(); - else if (error < 0 || - (error = git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)) < 0 || - (error = git_commit_tree(&ancestor_tree, ancestor_commit)) < 0) + if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || + (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) goto done; - if ((error = git_commit_tree(&our_tree, our_commit)) < 0 || - (error = git_commit_tree(&their_tree, their_commit)) < 0 || - (error = git_merge_trees(out, repo, ancestor_tree, our_tree, their_tree, opts)) < 0) - goto done; + error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); done: - git_commit_free(ancestor_commit); - git_tree_free(our_tree); - git_tree_free(their_tree); - git_tree_free(ancestor_tree); - + git_annotated_commit_free(ours); + git_annotated_commit_free(theirs); + git_annotated_commit_free(base); return error; } @@ -2099,7 +2427,7 @@ static int write_merge_msg( assert(repo && heads); entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); - GITERR_CHECK_ALLOC(entries); + GITERR_CHECK_ALLOC(entries); if (git_vector_init(&matching, heads_len, NULL) < 0) { git__free(entries); @@ -2153,7 +2481,7 @@ static int write_merge_msg( if (matching.length) sep =','; - + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || (error = merge_msg_write_tags(&file, &matching, sep)) < 0) goto cleanup; @@ -2266,49 +2594,50 @@ const char *merge_their_label(const char *branchname) } static int merge_normalize_checkout_opts( + git_checkout_options *out, git_repository *repo, - git_checkout_options *checkout_opts, const git_checkout_options *given_checkout_opts, - const git_annotated_commit *ancestor_head, + unsigned int checkout_strategy, + git_annotated_commit *ancestor, const git_annotated_commit *our_head, - size_t their_heads_len, - const git_annotated_commit **their_heads) + const git_annotated_commit **their_heads, + size_t their_heads_len) { + git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; int error = 0; GIT_UNUSED(repo); if (given_checkout_opts != NULL) - memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options)); - else { - git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); + else + memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); - memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options)); - } + out->checkout_strategy = checkout_strategy; - /* TODO: for multiple ancestors in merge-recursive, this is "merged common ancestors" */ - if (!checkout_opts->ancestor_label) { - if (ancestor_head && ancestor_head->commit) - checkout_opts->ancestor_label = git_commit_summary(ancestor_head->commit); + if (!out->ancestor_label) { + if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) + out->ancestor_label = git_commit_summary(ancestor->commit); + else if (ancestor) + out->ancestor_label = "merged common ancestors"; else - checkout_opts->ancestor_label = "ancestor"; + out->ancestor_label = "empty base"; } - if (!checkout_opts->our_label) { + if (!out->our_label) { if (our_head && our_head->ref_name) - checkout_opts->our_label = our_head->ref_name; + out->our_label = our_head->ref_name; else - checkout_opts->our_label = "ours"; + out->our_label = "ours"; } - if (!checkout_opts->their_label) { + if (!out->their_label) { if (their_heads_len == 1 && their_heads[0]->ref_name) - checkout_opts->their_label = merge_their_label(their_heads[0]->ref_name); + out->their_label = merge_their_label(their_heads[0]->ref_name); else if (their_heads_len == 1) - checkout_opts->their_label = their_heads[0]->id_str; + out->their_label = their_heads[0]->id_str; else - checkout_opts->their_label = "theirs"; + out->their_label = "theirs"; } return error; @@ -2319,6 +2648,7 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index git_tree *head_tree = NULL; git_index *index_repo = NULL; git_iterator *iter_repo = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *staged_diff_list = NULL, *index_diff_list = NULL; git_diff_delta *delta; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; @@ -2348,11 +2678,12 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index goto done; } - opts.pathspec.count = staged_paths.length; - opts.pathspec.strings = (char **)staged_paths.contents; + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + iter_opts.pathlist.strings = (char **)staged_paths.contents; + iter_opts.pathlist.count = staged_paths.length; - if ((error = git_iterator_for_index(&iter_repo, index_repo, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) goto done; @@ -2396,6 +2727,7 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde * will be applied by the merge (including conflicts). Ensure that there * are no changes in the workdir to these paths. */ + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; opts.pathspec.count = merged_paths->length; opts.pathspec.strings = (char **)merged_paths->contents; @@ -2414,6 +2746,7 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) { git_tree *head_tree = NULL; git_iterator *iter_head = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *merged_list = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_delta *delta; @@ -2422,9 +2755,11 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) const git_index_entry *e; int error = 0; + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) goto done; @@ -2655,11 +2990,10 @@ int git_merge( { git_reference *our_ref = NULL; git_checkout_options checkout_opts; - git_annotated_commit *ancestor_head = NULL, *our_head = NULL; - git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL; + git_annotated_commit *our_head = NULL, *base = NULL; git_index *index = NULL; git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - size_t i; + unsigned int checkout_strategy; int error = 0; assert(repo && their_heads); @@ -2669,61 +3003,49 @@ int git_merge( return -1; } - their_trees = git__calloc(their_heads_len, sizeof(git_tree *)); - GITERR_CHECK_ALLOC(their_trees); + if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) + goto done; - if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0 || - (error = merge_normalize_checkout_opts(repo, &checkout_opts, given_checkout_opts, - ancestor_head, our_head, their_heads_len, their_heads)) < 0 || - (error = git_indexwriter_init_for_operation(&indexwriter, repo, &checkout_opts.checkout_strategy)) < 0) - goto on_error; + checkout_strategy = given_checkout_opts ? + given_checkout_opts->checkout_strategy : + GIT_CHECKOUT_SAFE; - /* Write the merge files to the repository. */ - if ((error = git_merge__setup(repo, our_head, their_heads, their_heads_len)) < 0) - goto on_error; + if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, + &checkout_strategy)) < 0) + goto done; - if (ancestor_head != NULL && - (error = git_commit_tree(&ancestor_tree, ancestor_head->commit)) < 0) - goto on_error; + /* Write the merge setup files to the repository. */ + if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || + (error = git_merge__setup(repo, our_head, their_heads, + their_heads_len)) < 0) + goto done; - if ((error = git_commit_tree(&our_tree, our_head->commit)) < 0) - goto on_error; + /* TODO: octopus */ - for (i = 0; i < their_heads_len; i++) { - if ((error = git_commit_tree(&their_trees[i], their_heads[i]->commit)) < 0) - goto on_error; - } - - /* TODO: recursive, octopus, etc... */ - - if ((error = git_merge_trees(&index, repo, ancestor_tree, our_tree, their_trees[0], merge_opts)) < 0 || + if ((error = merge_annotated_commits(&index, &base, repo, our_head, + (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || - (error = git_checkout_index(repo, index, &checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto on_error; + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) + goto done; - goto done; + /* check out the merge results */ -on_error: - merge_state_cleanup(repo); + if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, + given_checkout_opts, checkout_strategy, + base, our_head, their_heads, their_heads_len)) < 0 || + (error = git_checkout_index(repo, index, &checkout_opts)) < 0) + goto done; + + error = git_indexwriter_commit(&indexwriter); done: + if (error < 0) + merge_state_cleanup(repo); + git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - - git_tree_free(ancestor_tree); - git_tree_free(our_tree); - - for (i = 0; i < their_heads_len; i++) - git_tree_free(their_trees[i]); - - git__free(their_trees); - git_annotated_commit_free(our_head); - git_annotated_commit_free(ancestor_head); - + git_annotated_commit_free(base); git_reference_free(our_ref); return error; diff --git a/src/merge.h b/src/merge.h index 3caf617c6..bd839be49 100644 --- a/src/merge.h +++ b/src/merge.h @@ -19,8 +19,8 @@ #define GIT_MERGE_MODE_FILE "MERGE_MODE" #define GIT_MERGE_FILE_MODE 0666 -#define GIT_MERGE_TREE_RENAME_THRESHOLD 50 -#define GIT_MERGE_TREE_TARGET_LIMIT 1000 +#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 +#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 /** Types of changes when files are merged from branch to branch. */ typedef enum { diff --git a/src/merge_file.c b/src/merge_file.c index 6d89b089d..6d4738065 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -7,17 +7,23 @@ #include "common.h" #include "repository.h" -#include "merge_file.h" #include "posix.h" #include "fileops.h" #include "index.h" +#include "diff_xdiff.h" #include "git2/repository.h" #include "git2/object.h" #include "git2/index.h" +#include "git2/merge.h" #include "xdiff/xdiff.h" +/* only examine the first 8000 bytes for binaryness. + * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197 + */ +#define GIT_MERGE_FILE_BINARY_SIZE 8000 + #define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) GIT_INLINE(const char *) merge_file_best_path( @@ -100,7 +106,7 @@ static void merge_file_normalize_opts( } } -static int git_merge_file__from_inputs( +static int merge_file__xdiff( git_merge_file_result *out, const git_merge_file_input *ancestor, const git_merge_file_input *ours, @@ -189,6 +195,63 @@ done: return error; } +static bool merge_file__is_binary(const git_merge_file_input *file) +{ + size_t len = file ? file->size : 0; + + if (len > GIT_XDIFF_MAX_SIZE) + return true; + if (len > GIT_MERGE_FILE_BINARY_SIZE) + len = GIT_MERGE_FILE_BINARY_SIZE; + + return len ? (memchr(file->ptr, 0, len) != NULL) : false; +} + +static int merge_file__binary( + git_merge_file_result *out, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + const git_merge_file_input *favored = NULL; + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS) + favored = ours; + else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS) + favored = theirs; + else + goto done; + + if ((out->path = git__strdup(favored->path)) == NULL || + (out->ptr = git__malloc(favored->size)) == NULL) + goto done; + + memcpy((char *)out->ptr, favored->ptr, favored->size); + out->len = favored->size; + out->mode = favored->mode; + out->automergeable = 1; + +done: + return 0; +} + +static int merge_file__from_inputs( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + if (merge_file__is_binary(ancestor) || + merge_file__is_binary(ours) || + merge_file__is_binary(theirs)) + return merge_file__binary(out, ours, theirs, given_opts); + + return merge_file__xdiff(out, ancestor, ours, theirs, given_opts); +} + static git_merge_file_input *git_merge_file__normalize_inputs( git_merge_file_input *out, const git_merge_file_input *given) @@ -223,7 +286,7 @@ int git_merge_file( ours = git_merge_file__normalize_inputs(&inputs[1], ours); theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); - return git_merge_file__from_inputs(out, ancestor, ours, theirs, options); + return merge_file__from_inputs(out, ancestor, ours, theirs, options); } int git_merge_file_from_index( @@ -234,8 +297,8 @@ int git_merge_file_from_index( const git_index_entry *theirs, const git_merge_file_options *options) { - git_merge_file_input inputs[3] = { {0} }, - *ancestor_input = NULL, *our_input = NULL, *their_input = NULL; + git_merge_file_input *ancestor_ptr = NULL, + ancestor_input = {0}, our_input = {0}, their_input = {0}; git_odb *odb = NULL; git_odb_object *odb_object[3] = { 0 }; int error = 0; @@ -249,27 +312,20 @@ int git_merge_file_from_index( if (ancestor) { if ((error = git_merge_file__input_from_index( - &inputs[0], &odb_object[0], odb, ancestor)) < 0) + &ancestor_input, &odb_object[0], odb, ancestor)) < 0) goto done; - ancestor_input = &inputs[0]; + ancestor_ptr = &ancestor_input; } if ((error = git_merge_file__input_from_index( - &inputs[1], &odb_object[1], odb, ours)) < 0) + &our_input, &odb_object[1], odb, ours)) < 0 || + (error = git_merge_file__input_from_index( + &their_input, &odb_object[2], odb, theirs)) < 0) goto done; - our_input = &inputs[1]; - - if ((error = git_merge_file__input_from_index( - &inputs[2], &odb_object[2], odb, theirs)) < 0) - goto done; - - their_input = &inputs[2]; - - if ((error = git_merge_file__from_inputs(out, - ancestor_input, our_input, their_input, options)) < 0) - goto done; + error = merge_file__from_inputs(out, + ancestor_ptr, &our_input, &their_input, options); done: git_odb_object_free(odb_object[0]); @@ -286,7 +342,5 @@ void git_merge_file_result_free(git_merge_file_result *result) return; git__free((char *)result->path); - - /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */ - free((char *)result->ptr); + git__free((char *)result->ptr); } diff --git a/src/netops.c b/src/netops.c index 5e8075597..c4241989f 100644 --- a/src/netops.c +++ b/src/netops.c @@ -261,6 +261,10 @@ int gitno_extract_url_parts( *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; } diff --git a/src/notes.c b/src/notes.c index ef4b41b31..fe8d2164f 100644 --- a/src/notes.c +++ b/src/notes.c @@ -663,7 +663,7 @@ int git_note_iterator_new( if (error < 0) goto cleanup; - if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) git_iterator_free(*it); cleanup: diff --git a/src/object.c b/src/object.c index 1073559fd..ebf77fb47 100644 --- a/src/object.c +++ b/src/object.c @@ -14,7 +14,7 @@ #include "blob.h" #include "tag.h" -static const int OBJECT_BASE_SIZE = 4096; +bool git_object__strict_input_validation = true; typedef struct { const char *str; /* type name string */ @@ -467,3 +467,27 @@ int git_object_short_id(git_buf *out, const git_object *obj) return error; } +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_otype expected_type) +{ + git_odb *odb; + git_otype actual_type; + size_t len; + int error; + + if (!git_object__strict_input_validation) + return true; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0) + return false; + + if (expected_type != GIT_OBJ_ANY && expected_type != actual_type) { + giterr_set(GITERR_INVALID, + "the requested type does not match the type in the ODB"); + return false; + } + + return true; +} + diff --git a/src/object.h b/src/object.h index d187c55b7..dd227d16d 100644 --- a/src/object.h +++ b/src/object.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_object_h__ #define INCLUDE_object_h__ +#include "repository.h" + +extern bool git_object__strict_input_validation; + /** Base git object for inheritance */ struct git_object { git_cached_obj cached; @@ -28,4 +32,23 @@ int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_otype expected_type); + +GIT_INLINE(git_otype) git_object__type_from_filemode(git_filemode_t mode) +{ + switch (mode) { + case GIT_FILEMODE_TREE: + return GIT_OBJ_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJ_COMMIT; + case GIT_FILEMODE_BLOB: + case GIT_FILEMODE_BLOB_EXECUTABLE: + case GIT_FILEMODE_LINK: + return GIT_OBJ_BLOB; + default: + return GIT_OBJ_BAD; + } +} + #endif diff --git a/src/odb.c b/src/odb.c index b2d635109..1c877c9fc 100644 --- a/src/odb.c +++ b/src/odb.c @@ -374,10 +374,14 @@ static int backend_sort_cmp(const void *a, const void *b) const backend_internal *backend_a = (const backend_internal *)(a); const backend_internal *backend_b = (const backend_internal *)(b); - if (backend_a->is_alternate == backend_b->is_alternate) - return (backend_b->priority - backend_a->priority); - - return backend_a->is_alternate ? 1 : -1; + if (backend_b->priority == backend_a->priority) { + if (backend_a->is_alternate) + return -1; + if (backend_b->is_alternate) + return 1; + return 0; + } + return (backend_b->priority - backend_a->priority); } int git_odb_new(git_odb **out) @@ -600,8 +604,7 @@ static void odb_free(git_odb *db) backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *backend = internal->backend; - if (backend->free) backend->free(backend); - else git__free(backend); + backend->free(backend); git__free(internal); } @@ -621,23 +624,18 @@ void git_odb_free(git_odb *db) GIT_REFCOUNT_DEC(db, odb_free); } -int git_odb_exists(git_odb *db, const git_oid *id) +static int odb_exists_1(git_odb *db, const git_oid *id, bool only_refreshed) { - git_odb_object *object; size_t i; bool found = false; - assert(db && id); - - if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { - git_odb_object_free(object); - return (int)true; - } - for (i = 0; i < db->backends.length && !found; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; + if (only_refreshed && !b->refresh) + continue; + if (b->exists != NULL) found = (bool)b->exists(b, id); } @@ -645,12 +643,74 @@ int git_odb_exists(git_odb *db, const git_oid *id) return (int)found; } +int git_odb_exists(git_odb *db, const git_oid *id) +{ + git_odb_object *object; + + assert(db && id); + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + git_odb_object_free(object); + return (int)true; + } + + if (odb_exists_1(db, id, false)) + return 1; + + if (!git_odb_refresh(db)) + return odb_exists_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + +static int odb_exists_prefix_1(git_oid *out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) +{ + size_t i; + int error = GIT_ENOTFOUND, num_found = 0; + git_oid last_found = {{0}}, found; + + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->exists_prefix) + continue; + + error = b->exists_prefix(&found, b, key, len); + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + continue; + if (error) + return error; + + /* make sure found item doesn't introduce ambiguity */ + if (num_found) { + if (git_oid__cmp(&last_found, &found)) + return git_odb__error_ambiguous("multiple matches for prefix"); + } else { + git_oid_cpy(&last_found, &found); + num_found++; + } + } + + if (!num_found) + return GIT_ENOTFOUND; + + if (out) + git_oid_cpy(out, &last_found); + + return 0; +} + int git_odb_exists_prefix( git_oid *out, git_odb *db, const git_oid *short_id, size_t len) { - int error = GIT_ENOTFOUND, num_found = 0; - size_t i; - git_oid key = {{0}}, last_found = {{0}}, found; + int error; + git_oid key = {{0}}; assert(db && short_id); @@ -674,35 +734,15 @@ int git_odb_exists_prefix( if (len & 1) key.id[len / 2] &= 0xF0; - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; + error = odb_exists_prefix_1(out, db, &key, len, false); - if (!b->exists_prefix) - continue; + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_exists_prefix_1(out, db, &key, len, true); - error = b->exists_prefix(&found, b, &key, len); - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) - continue; - if (error) - return error; - - /* make sure found item doesn't introduce ambiguity */ - if (num_found) { - if (git_oid__cmp(&last_found, &found)) - return git_odb__error_ambiguous("multiple matches for prefix"); - } else { - git_oid_cpy(&last_found, &found); - num_found++; - } - } - - if (!num_found) + if (error == GIT_ENOTFOUND) return git_odb__error_notfound("no match for id prefix", &key); - if (out) - git_oid_cpy(out, &last_found); - return 0; + return error; } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) @@ -784,36 +824,38 @@ static int hardcoded_objects(git_rawobj *raw, const git_oid *id) } } -int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) +static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, + bool only_refreshed) { - size_t i, reads = 0; - int error; + size_t i; git_rawobj raw; git_odb_object *object; + bool found = false; - assert(out && db && id); + if (!hardcoded_objects(&raw, id)) + found = true; - *out = git_cache_get_raw(odb_cache(db), id); - if (*out != NULL) - return 0; - - error = hardcoded_objects(&raw, id); - - for (i = 0; i < db->backends.length && error < 0; ++i) { + for (i = 0; i < db->backends.length && !found; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; + if (only_refreshed && !b->refresh) + continue; + if (b->read != NULL) { - ++reads; - error = b->read(&raw.data, &raw.len, &raw.type, b, id); + int error = b->read(&raw.data, &raw.len, &raw.type, b, id); + if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; + + found = true; } } - if (error && error != GIT_PASSTHROUGH) { - if (!reads) - return git_odb__error_notfound("no match for id", id); - return error; - } + if (!found) + return GIT_ENOTFOUND; giterr_clear(); if ((object = odb_object__alloc(id, &raw)) == NULL) @@ -823,42 +865,48 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) return 0; } -int git_odb_read_prefix( - git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) +int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) +{ + int error; + + assert(out && db && id); + + *out = git_cache_get_raw(odb_cache(db), id); + if (*out != NULL) + return 0; + + error = odb_read_1(out, db, id, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_1(out, db, id, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for id", id); + + return error; +} + +static int read_prefix_1(git_odb_object **out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) { size_t i; int error = GIT_ENOTFOUND; - git_oid key = {{0}}, found_full_oid = {{0}}; + git_oid found_full_oid = {{0}}; git_rawobj raw; void *data = NULL; bool found = false; git_odb_object *object; - assert(out && db); - - if (len < GIT_OID_MINPREFIXLEN) - return git_odb__error_ambiguous("prefix length too short"); - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - if (len == GIT_OID_HEXSZ) { - *out = git_cache_get_raw(odb_cache(db), short_id); - if (*out != NULL) - return 0; - } - - /* just copy valid part of short_id */ - memcpy(&key.id, short_id->id, (len + 1) / 2); - if (len & 1) - key.id[len / 2] &= 0xF0; - for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; + if (only_refreshed && !b->refresh) + continue; + if (b->read_prefix != NULL) { git_oid full_oid; - error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, &key, len); + error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) continue; @@ -879,7 +927,7 @@ int git_odb_read_prefix( } if (!found) - return git_odb__error_notfound("no match for prefix", &key); + return GIT_ENOTFOUND; if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) return -1; @@ -888,6 +936,42 @@ int git_odb_read_prefix( return 0; } +int git_odb_read_prefix( + git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) +{ + git_oid key = {{0}}; + int error; + + assert(out && db); + + if (len < GIT_OID_MINPREFIXLEN) + return git_odb__error_ambiguous("prefix length too short"); + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + if (len == GIT_OID_HEXSZ) { + *out = git_cache_get_raw(odb_cache(db), short_id); + if (*out != NULL) + return 0; + } + + /* just copy valid part of short_id */ + memcpy(&key.id, short_id->id, (len + 1) / 2); + if (len & 1) + key.id[len / 2] &= 0xF0; + + error = read_prefix_1(out, db, &key, len, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = read_prefix_1(out, db, &key, len, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for prefix", &key); + + return error; +} + int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) { unsigned int i; diff --git a/src/odb_loose.c b/src/odb_loose.c index 99b8f7c91..730c4b1e1 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -84,9 +84,9 @@ static int object_file_name( static int object_mkdir(const git_buf *name, const loose_backend *be) { - return git_futils_mkdir( + return git_futils_mkdir_relative( name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); + 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) diff --git a/src/odb_mempack.c b/src/odb_mempack.c index 34355270f..594a2784c 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -154,12 +154,16 @@ void git_mempack_reset(git_odb_backend *_backend) }); git_array_clear(db->commits); + + git_oidmap_clear(db->objects); } static void impl__free(git_odb_backend *_backend) { - git_mempack_reset(_backend); - git__free(_backend); + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + + git_oidmap_free(db->objects); + git__free(db); } int git_mempack_new(git_odb_backend **out) diff --git a/src/odb_pack.c b/src/odb_pack.c index 735158d96..77d2c75b9 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -346,7 +346,7 @@ static int pack_backend__refresh(git_odb_backend *backend_) return error; } -static int pack_backend__read_header_internal( +static int pack_backend__read_header( size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid) { @@ -361,24 +361,7 @@ static int pack_backend__read_header_internal( return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); } -static int pack_backend__read_header( - size_t *len_p, git_otype *type_p, - struct git_odb_backend *backend, const git_oid *oid) -{ - int error; - - error = pack_backend__read_header_internal(len_p, type_p, backend, oid); - - if (error != GIT_ENOTFOUND) - return error; - - if ((error = pack_backend__refresh(backend)) < 0) - return error; - - return pack_backend__read_header_internal(len_p, type_p, backend, oid); -} - -static int pack_backend__read_internal( +static int pack_backend__read( void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { @@ -397,24 +380,7 @@ static int pack_backend__read_internal( return 0; } -static int pack_backend__read( - void **buffer_p, size_t *len_p, git_otype *type_p, - git_odb_backend *backend, const git_oid *oid) -{ - int error; - - error = pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid); - - if (error != GIT_ENOTFOUND) - return error; - - if ((error = pack_backend__refresh(backend)) < 0) - return error; - - return pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid); -} - -static int pack_backend__read_prefix_internal( +static int pack_backend__read_prefix( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -451,45 +417,9 @@ static int pack_backend__read_prefix_internal( return error; } -static int pack_backend__read_prefix( - git_oid *out_oid, - void **buffer_p, - size_t *len_p, - git_otype *type_p, - git_odb_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error; - - error = pack_backend__read_prefix_internal( - out_oid, buffer_p, len_p, type_p, backend, short_oid, len); - - if (error != GIT_ENOTFOUND) - return error; - - if ((error = pack_backend__refresh(backend)) < 0) - return error; - - return pack_backend__read_prefix_internal( - out_oid, buffer_p, len_p, type_p, backend, short_oid, len); -} - static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; - int error; - - error = pack_entry_find(&e, (struct pack_backend *)backend, oid); - - if (error != GIT_ENOTFOUND) - return error == 0; - - if ((error = pack_backend__refresh(backend)) < 0) { - giterr_clear(); - return (int)false; - } - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } @@ -501,12 +431,7 @@ static int pack_backend__exists_prefix( struct git_pack_entry e = {0}; error = pack_entry_find_prefix(&e, pb, short_id, len); - - if (error == GIT_ENOTFOUND && !(error = pack_backend__refresh(backend))) - error = pack_entry_find_prefix(&e, pb, short_id, len); - git_oid_cpy(out, &e.sha1); - return error; } @@ -674,7 +599,6 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) git_path_isdir(git_buf_cstr(&path))) { backend->pack_folder = git_buf_detach(&path); - error = pack_backend__refresh((git_odb_backend *)backend); } diff --git a/src/oidmap.h b/src/oidmap.h index d2c451e7f..2cf208f53 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -49,4 +49,6 @@ GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) #define git_oidmap_size(h) kh_size(h) +#define git_oidmap_clear(h) kh_clear(oid, h) + #endif diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 4df7c6b7c..97736b714 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -15,6 +15,7 @@ #include "socket_stream.h" #include "netops.h" #include "git2/transport.h" +#include "git2/sys/openssl.h" #ifdef GIT_CURL # include "curl_stream.h" @@ -31,6 +32,115 @@ #include #include +SSL_CTX *git__ssl_ctx; + +#ifdef GIT_THREADS + +static git_mutex *openssl_locks; + +static void openssl_locking_function( + int mode, int n, const char *file, int line) +{ + int lock; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + lock = mode & CRYPTO_LOCK; + + if (lock) { + git_mutex_lock(&openssl_locks[n]); + } else { + git_mutex_unlock(&openssl_locks[n]); + } +} + +static void shutdown_ssl_locking(void) +{ + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < num_locks; ++i) + git_mutex_free(openssl_locks); + git__free(openssl_locks); +} + +#endif /* GIT_THREADS */ + +/** + * This function aims to clean-up the SSL context which + * we allocated. + */ +static void shutdown_ssl(void) +{ + if (git__ssl_ctx) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } +} + +int git_openssl_stream_global_init(void) +{ +#ifdef GIT_OPENSSL + long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + + /* Older OpenSSL and MacOS OpenSSL doesn't have this */ +#ifdef SSL_OP_NO_COMPRESSION + ssl_opts |= SSL_OP_NO_COMPRESSION; +#endif + + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + /* + * Load SSLv{2,3} and TLSv1 so that we can talk with servers + * which use the SSL hellos, which are often used for + * compatibility. We then disable SSL so we only allow OpenSSL + * to speak TLSv1 to perform the encryption itself. + */ + git__ssl_ctx = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_options(git__ssl_ctx, ssl_opts); + SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + return -1; + } +#endif + + git__on_shutdown(shutdown_ssl); + + return 0; +} + +int git_openssl_set_locking(void) +{ +#ifdef GIT_THREADS + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + GITERR_CHECK_ALLOC(openssl_locks); + + for (i = 0; i < num_locks; i++) { + if (git_mutex_init(&openssl_locks[i]) != 0) { + giterr_set(GITERR_SSL, "failed to initialize openssl locks"); + return -1; + } + } + + CRYPTO_set_locking_callback(openssl_locking_function); + git__on_shutdown(shutdown_ssl_locking); + return 0; +#else + giterr_set(GITERR_THREAD, "libgit2 as not built with threads"); + return -1; +#endif +} + + static int bio_create(BIO *b) { b->init = 1; @@ -158,7 +268,6 @@ static int ssl_teardown(SSL *ssl) else ret = 0; - SSL_free(ssl); return ret; } @@ -274,6 +383,8 @@ static int verify_server_cert(SSL *ssl, const char *host) GITERR_CHECK_ALLOC(peer_cn); memcpy(peer_cn, ASN1_STRING_data(str), size); peer_cn[size] = '\0'; + } else { + goto cert_fail_name; } } else { int size = ASN1_STRING_to_UTF8(&peer_cn, str); @@ -302,6 +413,7 @@ cert_fail_name: typedef struct { git_stream parent; git_stream *io; + bool connected; char *host; SSL *ssl; git_cert_x509 cert_info; @@ -318,6 +430,8 @@ int openssl_connect(git_stream *stream) if ((ret = git_stream_connect(st->io)) < 0) return ret; + st->connected = true; + bio = BIO_new(&git_stream_bio_method); GITERR_CHECK_ALLOC(bio); bio->ptr = st->io; @@ -360,11 +474,12 @@ int openssl_certificate(git_cert **out, git_stream *stream) return -1; } - st->cert_info.cert_type = GIT_CERT_X509; + st->cert_info.parent.cert_type = GIT_CERT_X509; st->cert_info.data = encoded_cert; st->cert_info.len = len; - *out = (git_cert *)&st->cert_info; + *out = &st->cert_info.parent; + return 0; } @@ -405,9 +520,11 @@ int openssl_close(git_stream *stream) openssl_stream *st = (openssl_stream *) stream; int ret; - if ((ret = ssl_teardown(st->ssl)) < 0) + if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) return -1; + st->connected = false; + return git_stream_close(st->io); } @@ -415,6 +532,7 @@ void openssl_free(git_stream *stream) { openssl_stream *st = (openssl_stream *) stream; + SSL_free(st->ssl); git__free(st->host); git__free(st->cert_info.data); git_stream_free(st->io); @@ -429,6 +547,7 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port) st = git__calloc(1, sizeof(openssl_stream)); GITERR_CHECK_ALLOC(st); + st->io = NULL; #ifdef GIT_CURL error = git_curl_stream_new(&st->io, host, port); #else @@ -436,12 +555,13 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port) #endif if (error < 0) - return error; + goto out_err; st->ssl = SSL_new(git__ssl_ctx); if (st->ssl == NULL) { giterr_set(GITERR_SSL, "failed to create ssl object"); - return -1; + error = -1; + goto out_err; } st->host = git__strdup(host); @@ -460,11 +580,29 @@ int git_openssl_stream_new(git_stream **out, const char *host, const char *port) *out = (git_stream *) st; return 0; + +out_err: + git_stream_free(st->io); + git__free(st); + + return error; } #else #include "stream.h" +#include "git2/sys/openssl.h" + +int git_openssl_stream_global_init(void) +{ + return 0; +} + +int git_openssl_set_locking(void) +{ + giterr_set(GITERR_SSL, "libgit2 was not built with OpenSSL support"); + return -1; +} int git_openssl_stream_new(git_stream **out, const char *host, const char *port) { diff --git a/src/openssl_stream.h b/src/openssl_stream.h index 9ca06489e..82b5110c4 100644 --- a/src/openssl_stream.h +++ b/src/openssl_stream.h @@ -9,6 +9,8 @@ #include "git2/sys/stream.h" +extern int git_openssl_stream_global_init(void); + extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); #endif diff --git a/src/pack-objects.c b/src/pack-objects.c index c4c061a3a..46fe8f3db 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -91,7 +91,7 @@ static unsigned name_hash(const char *name) static int packbuilder_config(git_packbuilder *pb) { git_config *config; - int ret; + int ret = 0; int64_t val; if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0) @@ -100,8 +100,10 @@ static int packbuilder_config(git_packbuilder *pb) #define config_get(KEY,DST,DFLT) do { \ ret = git_config_get_int64(&val, config, KEY); \ if (!ret) (DST) = val; \ - else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \ - else if (ret < 0) return -1; } while (0) + else if (ret == GIT_ENOTFOUND) { \ + (DST) = (DFLT); \ + ret = 0; \ + } else if (ret < 0) goto out; } while (0) config_get("pack.deltaCacheSize", pb->max_delta_cache_size, GIT_PACK_DELTA_CACHE_SIZE); @@ -113,9 +115,10 @@ static int packbuilder_config(git_packbuilder *pb) #undef config_get +out: git_config_free(config); - return 0; + return ret; } int git_packbuilder_new(git_packbuilder **out, git_repository *repo) @@ -135,8 +138,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) if (!pb->walk_objects) goto on_error; - if (git_pool_init(&pb->object_pool, sizeof(git_walk_object), 0) < 0) - goto on_error; + git_pool_init(&pb->object_pool, sizeof(git_walk_object)); pb->repo = repo; pb->nr_threads = 1; /* do not spawn any thread by default */ @@ -606,6 +608,7 @@ static git_pobject **compute_write_order(git_packbuilder *pb) } if (wo_end != pb->nr_objects) { + git__free(wo); giterr_set(GITERR_INVALID, "invalid write order"); return NULL; } @@ -626,10 +629,8 @@ static int write_pack(git_packbuilder *pb, int error = 0; write_order = compute_write_order(pb); - if (write_order == NULL) { - error = -1; - goto done; - } + if (write_order == NULL) + return -1; /* Write pack header */ ph.hdr_signature = htonl(PACK_SIGNATURE); @@ -847,9 +848,11 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, git_packbuilder__cache_unlock(pb); - if (overflow || - !(trg_object->delta_data = git__realloc(delta_buf, delta_size))) + if (overflow) return -1; + + trg_object->delta_data = git__realloc(delta_buf, delta_size); + GITERR_CHECK_ALLOC(trg_object->delta_data); } else { /* create delta when writing the pack */ git_packbuilder__cache_unlock(pb); diff --git a/src/pack.c b/src/pack.c index 45dd4d5be..52c652178 100644 --- a/src/pack.c +++ b/src/pack.c @@ -21,7 +21,7 @@ GIT__USE_OIDMAP static int packfile_open(struct git_pack_file *p); static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); -int packfile_unpack_compressed( +static int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p, git_mwindow **w_curs, @@ -365,9 +365,14 @@ static unsigned char *pack_window_open( * pointless to ask for an offset into the middle of that * hash, and the pack_window_contains function above wouldn't match * don't allow an offset too close to the end of the file. + * + * Don't allow a negative offset, as that means we've wrapped + * around. */ if (offset > (p->mwf.size - 20)) return NULL; + if (offset < 0) + return NULL; return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); } @@ -790,7 +795,6 @@ int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, obj->zstream.next_out = Z_NULL; st = inflateInit(&obj->zstream); if (st != Z_OK) { - git__free(obj); giterr_set(GITERR_ZLIB, "failed to init packfile stream"); return -1; } @@ -843,7 +847,7 @@ void git_packfile_stream_free(git_packfile_stream *obj) inflateEnd(&obj->zstream); } -int packfile_unpack_compressed( +static int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p, git_mwindow **w_curs, @@ -1176,6 +1180,7 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) { const unsigned char *index = p->index_map.data; + const unsigned char *end = index + p->index_map.len; index += 4 * 256; if (p->index_version == 1) { return ntohl(*((uint32_t *)(index + 24 * n))); @@ -1186,6 +1191,11 @@ static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_ if (!(off & 0x80000000)) return off; index += p->num_objects * 4 + (off & 0x7fffffff) * 8; + + /* Make sure we're not being sent out of bounds */ + if (index >= end - 8) + return -1; + return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | ntohl(*((uint32_t *)(index + 4))); } @@ -1265,6 +1275,7 @@ static int pack_entry_find_offset( const unsigned char *index = p->index_map.data; unsigned hi, lo, stride; int pos, found = 0; + git_off_t offset; const unsigned char *current = 0; *offset_out = 0; @@ -1337,7 +1348,12 @@ static int pack_entry_find_offset( if (found > 1) return git_odb__error_ambiguous("found multiple offsets for pack entry"); - *offset_out = nth_packed_object_offset(p, pos); + if ((offset = nth_packed_object_offset(p, pos)) < 0) { + giterr_set(GITERR_ODB, "packfile index is corrupt"); + return -1; + } + + *offset_out = offset; git_oid_fromraw(found_oid, current); #ifdef INDEX_DEBUG_LOOKUP diff --git a/src/pack.h b/src/pack.h index b3d5b2993..d15247b74 100644 --- a/src/pack.h +++ b/src/pack.h @@ -138,13 +138,6 @@ int git_packfile_resolve_header( git_off_t offset); int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset); -int packfile_unpack_compressed( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - git_off_t *curpos, - size_t size, - git_otype type); int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos); ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len); diff --git a/src/path.c b/src/path.c index 3e63f06d4..1fd14fcb9 100644 --- a/src/path.c +++ b/src/path.c @@ -10,7 +10,7 @@ #include "repository.h" #ifdef GIT_WIN32 #include "win32/posix.h" -#include "win32/buffer.h" +#include "win32/w32_buffer.h" #include "win32/w32_util.h" #include "win32/version.h" #else @@ -526,6 +526,17 @@ bool git_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +bool git_path_islink(const char *path) +{ + struct stat st; + + assert(path); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + #ifdef GIT_WIN32 bool git_path_is_empty_dir(const char *path) @@ -694,8 +705,7 @@ int git_path_resolve_relative(git_buf *path, size_t ceiling) char *base, *to, *from, *next; size_t len; - if (!path || git_buf_oom(path)) - return -1; + GITERR_CHECK_ALLOC_BUF(path); if (ceiling > path->size) ceiling = path->size; @@ -1166,7 +1176,11 @@ static int diriter_update_paths(git_path_diriter *diriter) diriter->path[path_len-1] = L'\0'; git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len); - git_buf_putc(&diriter->path_utf8, '/'); + + if (diriter->parent_utf8_len > 0 && + diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') + git_buf_putc(&diriter->path_utf8, '/'); + git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); if (git_buf_oom(&diriter->path_utf8)) @@ -1315,7 +1329,11 @@ int git_path_diriter_next(git_path_diriter *diriter) #endif git_buf_truncate(&diriter->path, diriter->parent_len); - git_buf_putc(&diriter->path, '/'); + + if (diriter->parent_len > 0 && + diriter->path.ptr[diriter->parent_len-1] != '/') + git_buf_putc(&diriter->path, '/'); + git_buf_put(&diriter->path, filename, filename_len); if (git_buf_oom(&diriter->path)) @@ -1380,7 +1398,7 @@ int git_path_dirload( git_vector *contents, const char *path, size_t prefix_len, - unsigned int flags) + uint32_t flags) { git_path_diriter iter = GIT_PATH_DIRITER_INIT; const char *name; @@ -1611,9 +1629,12 @@ static bool verify_component( !verify_dotgit_ntfs(repo, component, len)) return false; + /* don't bother rerunning the `.git` test if we ran the HFS or NTFS + * specific tests, they would have already rejected `.git`. + */ if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && - (flags & GIT_PATH_REJECT_DOT_GIT) && + (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL) && len == 4 && component[0] == '.' && (component[1] == 'g' || component[1] == 'G') && @@ -1630,6 +1651,8 @@ GIT_INLINE(unsigned int) dotgit_flags( { int protectHFS = 0, protectNTFS = 0; + flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; + #ifdef __APPLE__ protectHFS = 1; #endif @@ -1676,3 +1699,19 @@ bool git_path_isvalid( return verify_component(repo, start, (c - start), flags); } + +int git_path_normalize_slashes(git_buf *out, const char *path) +{ + int error; + char *p; + + if ((error = git_buf_puts(out, path)) < 0) + return error; + + for (p = out->ptr; *p; p++) { + if (*p == '\\') + *p = '/'; + } + + return 0; +} diff --git a/src/path.h b/src/path.h index e6be06faa..875c8cb7e 100644 --- a/src/path.h +++ b/src/path.h @@ -72,7 +72,7 @@ extern const char *git_path_topdir(const char *path); * This will return a number >= 0 which is the offset to the start of the * path, if the path is rooted (i.e. "/rooted/path" returns 0 and * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns < 0. + * returns -1. */ extern int git_path_root(const char *path); @@ -168,6 +168,12 @@ extern bool git_path_isdir(const char *path); */ extern bool git_path_isfile(const char *path); +/** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_path_islink(const char *path); + /** * Check if the given path is a directory, and is empty. */ @@ -558,15 +564,16 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or #define GIT_PATH_REJECT_TRAILING_COLON (1 << 6) #define GIT_PATH_REJECT_DOS_PATHS (1 << 7) #define GIT_PATH_REJECT_NT_CHARS (1 << 8) -#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9) -#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 10) +#define GIT_PATH_REJECT_DOT_GIT_LITERAL (1 << 9) +#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 10) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 11) /* Default path safety for writing files to disk: since we use the * Win32 "File Namespace" APIs ("\\?\") we need to protect from * paths that the normal Win32 APIs would not write. */ #ifdef GIT_WIN32 -# define GIT_PATH_REJECT_DEFAULTS \ +# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \ GIT_PATH_REJECT_TRAVERSAL | \ GIT_PATH_REJECT_BACKSLASH | \ GIT_PATH_REJECT_TRAILING_DOT | \ @@ -575,9 +582,18 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_NT_CHARS #else -# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL +# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL #endif + /* Paths that should never be written into the working directory. */ +#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ + GIT_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT + +/* Paths that should never be written to the index. */ +#define GIT_PATH_REJECT_INDEX_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT + /* * Determine whether a path is a valid git path or not - this must not contain * a '.' or '..' component, or a component that is ".git" (in any case). @@ -591,4 +607,9 @@ extern bool git_path_isvalid( const char *path, unsigned int flags); +/** + * Convert any backslashes into slashes + */ +int git_path_normalize_slashes(git_buf *out, const char *path); + #endif diff --git a/src/pathspec.c b/src/pathspec.c index fab6f9a76..8a93cdd50 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -237,9 +237,9 @@ int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) memset(ps, 0, sizeof(*ps)); ps->prefix = git_pathspec_prefix(paths); + git_pool_init(&ps->pool, 1); - if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 || - (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + if ((error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) git_pathspec__clear(ps); return error; @@ -312,15 +312,11 @@ static git_pathspec_match_list *pathspec_match_alloc( git_pathspec *ps, int datatype) { git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); - - if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) { - pathspec_match_free(m); - m = NULL; - } - if (!m) return NULL; + git_pool_init(&m->pool, 1); + /* need to keep reference to pathspec and increment refcount because * failures array stores pointers to the pattern strings of the * pathspec that had no matches @@ -524,16 +520,16 @@ int git_pathspec_match_workdir( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(repo); - if (!(error = git_iterator_for_workdir( - &iter, repo, NULL, NULL, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } @@ -546,16 +542,16 @@ int git_pathspec_match_index( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(index); - if (!(error = git_iterator_for_index( - &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } @@ -568,16 +564,16 @@ int git_pathspec_match_tree( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(tree); - if (!(error = git_iterator_for_tree( - &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } @@ -722,4 +718,3 @@ const char * git_pathspec_match_list_failed_entry( return entry ? *entry : NULL; } - diff --git a/src/pool.c b/src/pool.c index c93d78182..b4fc50fca 100644 --- a/src/pool.c +++ b/src/pool.c @@ -11,66 +11,144 @@ struct git_pool_page { GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); }; -struct pool_freelist { - struct pool_freelist *next; -}; - -#define GIT_POOL_MIN_USABLE 4 -#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*) - static void *pool_alloc_page(git_pool *pool, uint32_t size); -static void pool_insert_page(git_pool *pool, git_pool_page *page); -int git_pool_init( - git_pool *pool, uint32_t item_size, uint32_t items_per_page) +uint32_t git_pool__system_page_size(void) +{ + static uint32_t size = 0; + + if (!size) { + size_t page_size; + if (git__page_size(&page_size) < 0) + page_size = 4096; + /* allow space for malloc overhead */ + size = page_size - (2 * sizeof(void *)) - sizeof(git_pool_page); + } + + return size; +} + +#ifndef GIT_DEBUG_POOL +void git_pool_init(git_pool *pool, uint32_t item_size) { assert(pool); - - if (!item_size) - item_size = 1; - /* round up item_size for decent object alignment */ - if (item_size > 4) - item_size = (item_size + 7) & ~7; - else if (item_size == 3) - item_size = 4; - - if (!items_per_page) - items_per_page = git_pool__suggest_items_per_page(item_size); - if (item_size * items_per_page < GIT_POOL_MIN_PAGESZ) - items_per_page = (GIT_POOL_MIN_PAGESZ + item_size - 1) / item_size; + assert(item_size >= 1); memset(pool, 0, sizeof(git_pool)); pool->item_size = item_size; - pool->page_size = item_size * items_per_page; - - return 0; + pool->page_size = git_pool__system_page_size(); } void git_pool_clear(git_pool *pool) { git_pool_page *scan, *next; - for (scan = pool->open; scan != NULL; scan = next) { + for (scan = pool->pages; scan != NULL; scan = next) { next = scan->next; git__free(scan); } - pool->open = NULL; - for (scan = pool->full; scan != NULL; scan = next) { - next = scan->next; - git__free(scan); - } - pool->full = NULL; - - pool->free_list = NULL; - - pool->items = 0; - - pool->has_string_alloc = 0; - pool->has_multi_item_alloc = 0; - pool->has_large_page_alloc = 0; + pool->pages = NULL; } +static void *pool_alloc_page(git_pool *pool, uint32_t size) +{ + git_pool_page *page; + const uint32_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; + size_t alloc_size; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || + !(page = git__malloc(alloc_size))) + return NULL; + + page->size = new_page_size; + page->avail = new_page_size - size; + page->next = pool->pages; + + pool->pages = page; + + return page->data; +} + +static void *pool_alloc(git_pool *pool, uint32_t size) +{ + git_pool_page *page = pool->pages; + void *ptr = NULL; + + if (!page || page->avail < size) + return pool_alloc_page(pool, size); + + ptr = &page->data[page->size - page->avail]; + page->avail -= size; + + return ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +#else + +static int git_pool__ptr_cmp(const void * a, const void * b) +{ + if(a > b) { + return 1; + } + if(a < b) { + return -1; + } + else { + return 0; + } +} + +void git_pool_init(git_pool *pool, uint32_t item_size) +{ + assert(pool); + assert(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = git_pool__system_page_size(); + git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); +} + +void git_pool_clear(git_pool *pool) +{ + git_vector_free_deep(&pool->allocations); +} + +static void *pool_alloc(git_pool *pool, uint32_t size) { + void *ptr = NULL; + if((ptr = git__malloc(size)) == NULL) { + return NULL; + } + git_vector_insert_sorted(&pool->allocations, ptr, NULL); + return ptr; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + size_t pos; + return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; +} +#endif + void git_pool_swap(git_pool *a, git_pool *b) { git_pool temp; @@ -83,110 +161,29 @@ void git_pool_swap(git_pool *a, git_pool *b) memcpy(b, &temp, sizeof(temp)); } -static void pool_insert_page(git_pool *pool, git_pool_page *page) +static uint32_t alloc_size(git_pool *pool, uint32_t count) { - git_pool_page *scan; + const uint32_t align = sizeof(void *) - 1; - /* If there are no open pages or this page has the most open space, - * insert it at the beginning of the list. This is the common case. - */ - if (pool->open == NULL || pool->open->avail < page->avail) { - page->next = pool->open; - pool->open = page; - return; + if (pool->item_size > 1) { + const uint32_t item_size = (pool->item_size + align) & ~align; + return item_size * count; } - /* Otherwise insert into sorted position. */ - for (scan = pool->open; - scan->next && scan->next->avail > page->avail; - scan = scan->next); - page->next = scan->next; - scan->next = page; -} - -static void *pool_alloc_page(git_pool *pool, uint32_t size) -{ - git_pool_page *page; - uint32_t new_page_size; - size_t alloc_size; - - if (size <= pool->page_size) - new_page_size = pool->page_size; - else { - new_page_size = size; - pool->has_large_page_alloc = 1; - } - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || - !(page = git__calloc(1, alloc_size))) - return NULL; - - page->size = new_page_size; - page->avail = new_page_size - size; - - if (page->avail > 0) - pool_insert_page(pool, page); - else { - page->next = pool->full; - pool->full = page; - } - - pool->items++; - - return page->data; -} - -GIT_INLINE(void) pool_remove_page( - git_pool *pool, git_pool_page *page, git_pool_page *prev) -{ - if (prev == NULL) - pool->open = page->next; - else - prev->next = page->next; + return (count + align) & ~align; } void *git_pool_malloc(git_pool *pool, uint32_t items) { - git_pool_page *scan = pool->open, *prev; - uint32_t size = ((items * pool->item_size) + 7) & ~7; - void *ptr = NULL; - - pool->has_string_alloc = 0; - if (items > 1) - pool->has_multi_item_alloc = 1; - else if (pool->free_list != NULL) { - ptr = pool->free_list; - pool->free_list = ((struct pool_freelist *)pool->free_list)->next; - return ptr; - } - - /* just add a block if there is no open one to accommodate this */ - if (size >= pool->page_size || !scan || scan->avail < size) - return pool_alloc_page(pool, size); - - pool->items++; - - /* find smallest block in free list with space */ - for (scan = pool->open, prev = NULL; - scan->next && scan->next->avail >= size; - prev = scan, scan = scan->next); - - /* allocate space from the block */ - ptr = &scan->data[scan->size - scan->avail]; - scan->avail -= size; - - /* move to full list if there is almost no space left */ - if (scan->avail < pool->item_size || scan->avail < GIT_POOL_MIN_USABLE) { - pool_remove_page(pool, scan, prev); - scan->next = pool->full; - pool->full = scan; - } - /* reorder list if block is now smaller than the one after it */ - else if (scan->next != NULL && scan->next->avail > scan->avail) { - pool_remove_page(pool, scan, prev); - pool_insert_page(pool, scan); - } + return pool_alloc(pool, alloc_size(pool, items)); +} +void *git_pool_mallocz(git_pool *pool, uint32_t items) +{ + const uint32_t size = alloc_size(pool, items); + void *ptr = pool_alloc(pool, size); + if (ptr) + memset(ptr, 0x0, size); return ptr; } @@ -204,15 +201,12 @@ char *git_pool_strndup(git_pool *pool, const char *str, size_t n) ptr[n] = '\0'; } - pool->has_string_alloc = 1; - return ptr; } char *git_pool_strdup(git_pool *pool, const char *str) { assert(pool && str && pool->item_size == sizeof(char)); - return git_pool_strndup(pool, str, strlen(str)); } @@ -238,88 +232,5 @@ char *git_pool_strcat(git_pool *pool, const char *a, const char *b) memcpy(((char *)ptr) + len_a, b, len_b); *(((char *)ptr) + len_a + len_b) = '\0'; } - pool->has_string_alloc = 1; - return ptr; } - -void git_pool_free(git_pool *pool, void *ptr) -{ - struct pool_freelist *item = ptr; - - assert(pool && pool->item_size >= sizeof(void*)); - - if (item) { - item->next = pool->free_list; - pool->free_list = item; - } -} - -void git_pool_free_array(git_pool *pool, size_t count, void **ptrs) -{ - struct pool_freelist **items = (struct pool_freelist **)ptrs; - size_t i; - - assert(pool && ptrs && pool->item_size >= sizeof(void*)); - - if (!count) - return; - - for (i = count - 1; i > 0; --i) - items[i]->next = items[i - 1]; - - items[i]->next = pool->free_list; - pool->free_list = items[count - 1]; -} - -uint32_t git_pool__open_pages(git_pool *pool) -{ - uint32_t ct = 0; - git_pool_page *scan; - for (scan = pool->open; scan != NULL; scan = scan->next) ct++; - return ct; -} - -uint32_t git_pool__full_pages(git_pool *pool) -{ - uint32_t ct = 0; - git_pool_page *scan; - for (scan = pool->full; scan != NULL; scan = scan->next) ct++; - return ct; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - git_pool_page *scan; - for (scan = pool->open; scan != NULL; scan = scan->next) - if ((void *)scan->data <= ptr && - (void *)(((char *)scan->data) + scan->size) > ptr) - return true; - for (scan = pool->full; scan != NULL; scan = scan->next) - if ((void *)scan->data <= ptr && - (void *)(((char *)scan->data) + scan->size) > ptr) - return true; - return false; -} - -uint32_t git_pool__system_page_size(void) -{ - static uint32_t size = 0; - - if (!size) { - size_t page_size; - if (git__page_size(&page_size) < 0) - page_size = 4096; - size = page_size - 2 * sizeof(void *); /* allow space for malloc overhead */ - } - - return size; -} - -uint32_t git_pool__suggest_items_per_page(uint32_t item_size) -{ - uint32_t page_bytes = - git_pool__system_page_size() - sizeof(git_pool_page); - return page_bytes / item_size; -} - diff --git a/src/pool.h b/src/pool.h index b0007f315..e0fafa997 100644 --- a/src/pool.h +++ b/src/pool.h @@ -8,9 +8,11 @@ #define INCLUDE_pool_h__ #include "common.h" +#include "vector.h" typedef struct git_pool_page git_pool_page; +#ifndef GIT_DEBUG_POOL /** * Chunked allocator. * @@ -28,37 +30,52 @@ typedef struct git_pool_page git_pool_page; * For examples of how to set up a `git_pool` see `git_pool_init`. */ typedef struct { - git_pool_page *open; /* pages with space left */ - git_pool_page *full; /* pages with no space left */ - void *free_list; /* optional: list of freed blocks */ + git_pool_page *pages; /* allocated pages */ uint32_t item_size; /* size of single alloc unit in bytes */ uint32_t page_size; /* size of page in bytes */ - uint32_t items; - unsigned has_string_alloc : 1; /* was the strdup function used */ - unsigned has_multi_item_alloc : 1; /* was items ever > 1 in malloc */ - unsigned has_large_page_alloc : 1; /* are any pages > page_size */ } git_pool; -#define GIT_POOL_INIT_STRINGPOOL { 0, 0, 0, 1, 4000, 0, 0, 0, 0 } +#else + +/** + * Debug chunked allocator. + * + * Acts just like `git_pool` but instead of actually pooling allocations it + * passes them through to `git__malloc`. This makes it possible to easily debug + * systems that use `git_pool` using valgrind. + * + * In order to track allocations during the lifetime of the pool we use a + * `git_vector`. When the pool is deallocated everything in the vector is + * freed. + * + * `API is exactly the same as the standard `git_pool` with one exception. + * Since we aren't allocating pages to hand out in chunks we can't easily + * implement `git_pool__open_pages`. + */ +typedef struct { + git_vector allocations; + uint32_t item_size; + uint32_t page_size; +} git_pool; +#endif /** * Initialize a pool. * * To allocation strings, use like this: * - * git_pool_init(&string_pool, 1, 0); + * git_pool_init(&string_pool, 1); * my_string = git_pool_strdup(&string_pool, your_string); * * To allocate items of fixed size, use like this: * - * git_pool_init(&pool, sizeof(item), 0); + * git_pool_init(&pool, sizeof(item)); * my_item = git_pool_malloc(&pool, 1); * * Of course, you can use this in other ways, but those are the * two most common patterns. */ -extern int git_pool_init( - git_pool *pool, uint32_t item_size, uint32_t items_per_page); +extern void git_pool_init(git_pool *pool, uint32_t item_size); /** * Free all items in pool @@ -74,17 +91,7 @@ extern void git_pool_swap(git_pool *a, git_pool *b); * Allocate space for one or more items from a pool. */ extern void *git_pool_malloc(git_pool *pool, uint32_t items); - -/** - * Allocate space and zero it out. - */ -GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items) -{ - void *ptr = git_pool_malloc(pool, items); - if (ptr) - memset(ptr, 0, (size_t)items * (size_t)pool->item_size); - return ptr; -} +extern void *git_pool_mallocz(git_pool *pool, uint32_t items); /** * Allocate space and duplicate string data into it. @@ -114,35 +121,12 @@ extern char *git_pool_strdup_safe(git_pool *pool, const char *str); */ extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); -/** - * Push a block back onto the free list for the pool. - * - * This is allowed only if the item_size is >= sizeof(void*). - * - * In some cases, it is helpful to "release" an allocated block - * for reuse. Pools don't support a general purpose free, but - * they will keep a simple free blocks linked list provided the - * native block size is large enough to hold a void pointer - */ -extern void git_pool_free(git_pool *pool, void *ptr); - -/** - * Push an array of pool allocated blocks efficiently onto the free list. - * - * This has the same constraints as `git_pool_free()` above. - */ -extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs); - /* * Misc utilities */ - +#ifndef GIT_DEBUG_POOL extern uint32_t git_pool__open_pages(git_pool *pool); - -extern uint32_t git_pool__full_pages(git_pool *pool); - +#endif extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); -extern uint32_t git_pool__suggest_items_per_page(uint32_t item_size); - #endif diff --git a/src/posix.c b/src/posix.c index 8d86aa8bf..c7201ba14 100644 --- a/src/posix.c +++ b/src/posix.c @@ -62,8 +62,11 @@ int p_getaddrinfo( ai = ainfo; for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { - ai->ai_next = malloc(sizeof(struct addrinfo)); - memcpy(&ai->ai_next, ainfo, sizeof(struct addrinfo)); + if (!(ai->ai_next = malloc(sizeof(struct addrinfo)))) { + p_freeaddrinfo(ainfo); + return -1; + } + memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); memcpy(&ai->ai_next->ai_addr_in.sin_addr, ainfo->ai_hostent->h_addr_list[p], ainfo->ai_hostent->h_length); diff --git a/src/push.c b/src/push.c index a0d8a0550..3c9fa2f1b 100644 --- a/src/push.c +++ b/src/push.c @@ -73,6 +73,7 @@ int git_push_set_options(git_push *push, const git_push_options *opts) GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); push->pb_parallelism = opts->pb_parallelism; + push->custom_headers = &opts->custom_headers; return 0; } @@ -638,7 +639,7 @@ int git_push_finish(git_push *push, const git_remote_callbacks *callbacks) int error; if (!git_remote_connected(push->remote) && - (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks)) < 0) + (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks, push->custom_headers)) < 0) return error; if ((error = filter_refs(push->remote)) < 0 || diff --git a/src/push.h b/src/push.h index a847ee0d0..e32ad2f4d 100644 --- a/src/push.h +++ b/src/push.h @@ -38,6 +38,7 @@ struct git_push { /* options */ unsigned pb_parallelism; + const git_strarray *custom_headers; }; /** diff --git a/src/rebase.c b/src/rebase.c index 17536c030..bcad9b7cd 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -63,17 +63,23 @@ struct git_rebase { char *state_path; int head_detached : 1, + inmemory : 1, quiet : 1, started : 1; - char *orig_head_name; + git_array_t(git_rebase_operation) operations; + size_t current; + + /* Used by in-memory rebase */ + git_index *index; + git_commit *last_commit; + + /* Used by regular (not in-memory) merge-style rebase */ git_oid orig_head_id; + char *orig_head_name; git_oid onto_id; char *onto_name; - - git_array_t(git_rebase_operation) operations; - size_t current; }; #define GIT_REBASE_STATE_INIT {0} @@ -251,12 +257,12 @@ done: return error; } -static git_rebase *rebase_alloc(const git_rebase_options *rebase_opts) +static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) { git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); + GITERR_CHECK_ALLOC(rebase); - if (!rebase) - return NULL; + *out = NULL; if (rebase_opts) memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); @@ -264,14 +270,16 @@ static git_rebase *rebase_alloc(const git_rebase_options *rebase_opts) git_rebase_init_options(&rebase->options, GIT_REBASE_OPTIONS_VERSION); if (rebase_opts && rebase_opts->rewrite_notes_ref) { - if ((rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref)) == NULL) - return NULL; + rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref); + GITERR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); } if ((rebase->options.checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0) rebase->options.checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; - return rebase; + *out = rebase; + + return 0; } static int rebase_check_versions(const git_rebase_options *given_opts) @@ -299,8 +307,8 @@ int git_rebase_open( if ((error = rebase_check_versions(given_opts)) < 0) return error; - rebase = rebase_alloc(given_opts); - GITERR_CHECK_ALLOC(rebase); + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; rebase->repo = repo; @@ -393,6 +401,9 @@ done: static int rebase_cleanup(git_rebase *rebase) { + if (!rebase || rebase->inmemory) + return 0; + return git_path_isdir(rebase->state_path) ? git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : 0; @@ -601,59 +612,63 @@ static int rebase_init_merge( const git_annotated_commit *upstream, const git_annotated_commit *onto) { - if (rebase_init_operations(rebase, repo, branch, upstream, onto) < 0) - return -1; + git_reference *head_ref = NULL; + git_commit *onto_commit = NULL; + git_buf reflog = GIT_BUF_INIT; + git_buf state_path = GIT_BUF_INIT; + int error; + + GIT_UNUSED(upstream); + + if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0) + goto done; + + rebase->state_path = git_buf_detach(&state_path); + GITERR_CHECK_ALLOC(rebase->state_path); + + rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD); + GITERR_CHECK_ALLOC(rebase->orig_head_name); rebase->onto_name = git__strdup(rebase_onto_name(onto)); GITERR_CHECK_ALLOC(rebase->onto_name); - return 0; + rebase->quiet = rebase->options.quiet; + + git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); + git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); + + if ((error = rebase_setupfiles(rebase)) < 0 || + (error = git_buf_printf(&reflog, + "rebase: checkout %s", rebase_onto_name(onto))) < 0 || + (error = git_commit_lookup( + &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || + (error = git_checkout_tree(repo, + (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || + (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, + git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) + goto done; + +done: + git_reference_free(head_ref); + git_commit_free(onto_commit); + git_buf_free(&reflog); + git_buf_free(&state_path); + + return error; } -static int rebase_init( +static int rebase_init_inmemory( git_rebase *rebase, git_repository *repo, const git_annotated_commit *branch, const git_annotated_commit *upstream, const git_annotated_commit *onto) { - git_reference *head_ref = NULL; - git_annotated_commit *head_branch = NULL; - git_buf state_path = GIT_BUF_INIT; - int error; + GIT_UNUSED(branch); + GIT_UNUSED(upstream); - if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0) - goto done; - - if (!branch) { - if ((error = git_repository_head(&head_ref, repo)) < 0 || - (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) - goto done; - - branch = head_branch; - } - - rebase->repo = repo; - rebase->type = GIT_REBASE_TYPE_MERGE; - rebase->state_path = git_buf_detach(&state_path); - rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD); - rebase->quiet = rebase->options.quiet; - - git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); - git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); - - if (!rebase->orig_head_name || !rebase->state_path) - return -1; - - error = rebase_init_merge(rebase, repo, branch, upstream, onto); - - git_buf_free(&state_path); - -done: - git_reference_free(head_ref); - git_annotated_commit_free(head_branch); - - return error; + return git_commit_lookup( + &rebase->last_commit, repo, git_annotated_commit_id(onto)); } int git_rebase_init( @@ -665,9 +680,9 @@ int git_rebase_init( const git_rebase_options *given_opts) { git_rebase *rebase = NULL; - git_buf reflog = GIT_BUF_INIT; - git_commit *onto_commit = NULL; + git_annotated_commit *head_branch = NULL; git_reference *head_ref = NULL; + bool inmemory = (given_opts && given_opts->inmemory); int error; assert(repo && (upstream || onto)); @@ -677,39 +692,51 @@ int git_rebase_init( if (!onto) onto = upstream; - if ((error = rebase_check_versions(given_opts)) < 0 || - (error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || - (error = rebase_ensure_not_in_progress(repo)) < 0 || - (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0 || - (error = git_commit_lookup( - &onto_commit, repo, git_annotated_commit_id(onto))) < 0) - return error; - - rebase = rebase_alloc(given_opts); - - if ((error = rebase_init( - rebase, repo, branch, upstream, onto)) < 0 || - (error = rebase_setupfiles(rebase)) < 0 || - (error = git_buf_printf(&reflog, - "rebase: checkout %s", rebase_onto_name(onto))) < 0 || - (error = git_checkout_tree( - repo, (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || - (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, - git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) + if ((error = rebase_check_versions(given_opts)) < 0) goto done; - *out = rebase; + if (!inmemory) { + if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || + (error = rebase_ensure_not_in_progress(repo)) < 0 || + (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) + goto done; + } + + if (!branch) { + if ((error = git_repository_head(&head_ref, repo)) < 0 || + (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) + goto done; + + branch = head_branch; + } + + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; + + rebase->repo = repo; + rebase->inmemory = inmemory; + rebase->type = GIT_REBASE_TYPE_MERGE; + + if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) + goto done; + + if (inmemory) + error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); + else + rebase_init_merge(rebase, repo, branch ,upstream, onto); + + if (error == 0) + *out = rebase; done: git_reference_free(head_ref); + git_annotated_commit_free(head_branch); + if (error < 0) { rebase_cleanup(rebase); git_rebase_free(rebase); } - git_commit_free(onto_commit); - git_buf_free(&reflog); - return error; } @@ -764,9 +791,6 @@ static int rebase_next_merge( *out = NULL; - if ((error = rebase_movenext(rebase)) < 0) - goto done; - operation = git_array_get(rebase->operations, rebase->current); if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || @@ -791,7 +815,7 @@ static int rebase_next_merge( if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || (error = rebase_setupfile(rebase, MSGNUM_FILE, -1, "%" PRIuZ "\n", rebase->current+1)) < 0 || (error = rebase_setupfile(rebase, CURRENT_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, NULL)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || (error = git_merge__check_result(rebase->repo, index)) < 0 || (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || (error = git_indexwriter_commit(&indexwriter)) < 0) @@ -812,6 +836,49 @@ done: return error; } +static int rebase_next_inmemory( + git_rebase_operation **out, + git_rebase *rebase) +{ + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_rebase_operation *operation; + git_index *index = NULL; + int error; + + *out = NULL; + + operation = git_array_get(rebase->operations, rebase->current); + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0 || + (error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || + (error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) + goto done; + + if (!rebase->index) { + rebase->index = index; + index = NULL; + } else { + if ((error = git_index_read_index(rebase->index, index)) < 0) + goto done; + } + + *out = operation; + +done: + git_commit_free(current_commit); + git_commit_free(parent_commit); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_index_free(index); + + return error; +} + int git_rebase_next( git_rebase_operation **out, git_rebase *rebase) @@ -820,14 +887,91 @@ int git_rebase_next( assert(out && rebase); - switch (rebase->type) { - case GIT_REBASE_TYPE_MERGE: + if ((error = rebase_movenext(rebase)) < 0) + return error; + + if (rebase->inmemory) + error = rebase_next_inmemory(out, rebase); + else if (rebase->type == GIT_REBASE_TYPE_MERGE) error = rebase_next_merge(out, rebase); - break; - default: + else abort(); + + return error; +} + +int git_rebase_inmemory_index( + git_index **out, + git_rebase *rebase) +{ + assert(out && rebase && rebase->index); + + GIT_REFCOUNT_INC(rebase->index); + *out = rebase->index; + + return 0; +} + +static int rebase_commit__create( + git_commit **out, + git_rebase *rebase, + git_index *index, + git_commit *parent_commit, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_commit *current_commit = NULL, *commit = NULL; + git_tree *parent_tree = NULL, *tree = NULL; + git_oid tree_id, commit_id; + int error; + + operation = git_array_get(rebase->operations, rebase->current); + + if (git_index_has_conflicts(index)) { + giterr_set(GITERR_REBASE, "conflicts have not been resolved"); + error = GIT_EUNMERGED; + goto done; } + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || + (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || + (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) + goto done; + + if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { + giterr_set(GITERR_REBASE, "this patch has already been applied"); + error = GIT_EAPPLIED; + goto done; + } + + if (!author) + author = git_commit_author(current_commit); + + if (!message) { + message_encoding = git_commit_message_encoding(current_commit); + message = git_commit_message(current_commit); + } + + if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author, + committer, message_encoding, message, tree, 1, + (const git_commit **)&parent_commit)) < 0 || + (error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) + goto done; + + *out = commit; + +done: + if (error < 0) + git_commit_free(commit); + + git_commit_free(current_commit); + git_tree_free(parent_tree); + git_tree_free(tree); + return error; } @@ -839,79 +983,73 @@ static int rebase_commit_merge( const char *message_encoding, const char *message) { - git_index *index = NULL; - git_reference *head = NULL; - git_commit *current_commit = NULL, *head_commit = NULL, *commit = NULL; git_rebase_operation *operation; - git_tree *head_tree = NULL, *tree = NULL; - git_diff *diff = NULL; - git_oid tree_id; - git_buf reflog_msg = GIT_BUF_INIT; + git_reference *head = NULL; + git_commit *head_commit = NULL, *commit = NULL; + git_index *index = NULL; char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; int error; operation = git_array_get(rebase->operations, rebase->current); assert(operation); - if ((error = git_repository_index(&index, rebase->repo)) < 0) - goto done; - - if (git_index_has_conflicts(index)) { - giterr_set(GITERR_REBASE, "Conflicts have not been resolved"); - error = GIT_EUNMERGED; - goto done; - } - if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || - (error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || (error = git_repository_head(&head, rebase->repo)) < 0 || (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)) < 0 || - (error = git_commit_tree(&head_tree, head_commit)) < 0 || - (error = git_diff_tree_to_index(&diff, rebase->repo, head_tree, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(diff) == 0) { - giterr_set(GITERR_REBASE, "This patch has already been applied"); - error = GIT_EAPPLIED; - goto done; - } - - if ((error = git_index_write_tree(&tree_id, index)) < 0 || - (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) - goto done; - - if (!author) - author = git_commit_author(current_commit); - - if (!message) { - message_encoding = git_commit_message_encoding(current_commit); - message = git_commit_message(current_commit); - } - - if ((error = git_commit_create(commit_id, rebase->repo, NULL, author, - committer, message_encoding, message, tree, 1, - (const git_commit **)&head_commit)) < 0 || - (error = git_commit_lookup(&commit, rebase->repo, commit_id)) < 0 || + (error = git_repository_index(&index, rebase->repo)) < 0 || + (error = rebase_commit__create(&commit, rebase, index, head_commit, + author, committer, message_encoding, message)) < 0 || (error = git_reference__update_for_commit( - rebase->repo, NULL, "HEAD", commit_id, "rebase")) < 0) + rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) goto done; - git_oid_fmt(old_idstr, git_commit_id(current_commit)); - git_oid_fmt(new_idstr, commit_id); + git_oid_fmt(old_idstr, &operation->id); + git_oid_fmt(new_idstr, git_commit_id(commit)); - error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, - "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr); + if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, + "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) + goto done; + + git_oid_cpy(commit_id, git_commit_id(commit)); done: - git_buf_free(&reflog_msg); - git_commit_free(commit); - git_diff_free(diff); - git_tree_free(tree); - git_tree_free(head_tree); - git_commit_free(head_commit); - git_commit_free(current_commit); - git_reference_free(head); git_index_free(index); + git_reference_free(head); + git_commit_free(head_commit); + git_commit_free(commit); + return error; +} + +static int rebase_commit_inmemory( + git_oid *commit_id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_commit *commit = NULL; + int error = 0; + + operation = git_array_get(rebase->operations, rebase->current); + + assert(operation); + assert(rebase->index); + assert(rebase->last_commit); + + if ((error = rebase_commit__create(&commit, rebase, rebase->index, + rebase->last_commit, author, committer, message_encoding, message)) < 0) + goto done; + + git_commit_free(rebase->last_commit); + rebase->last_commit = commit; + + git_oid_cpy(commit_id, git_commit_id(commit)); + +done: + if (error < 0) + git_commit_free(commit); return error; } @@ -928,14 +1066,14 @@ int git_rebase_commit( assert(rebase && committer); - switch (rebase->type) { - case GIT_REBASE_TYPE_MERGE: + if (rebase->inmemory) + error = rebase_commit_inmemory( + id, rebase, author, committer, message_encoding, message); + else if (rebase->type == GIT_REBASE_TYPE_MERGE) error = rebase_commit_merge( id, rebase, author, committer, message_encoding, message); - break; - default: + else abort(); - } return error; } @@ -948,6 +1086,9 @@ int git_rebase_abort(git_rebase *rebase) assert(rebase); + if (rebase->inmemory) + return 0; + error = rebase->head_detached ? git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, &rebase->orig_head_id, 1, "rebase: aborting") : @@ -1125,6 +1266,9 @@ int git_rebase_finish( assert(rebase); + if (rebase->inmemory) + return 0; + git_oid_fmt(onto, &rebase->onto_id); if ((error = git_buf_printf(&branch_msg, "rebase finished: %s onto %.*s", @@ -1182,6 +1326,8 @@ void git_rebase_free(git_rebase *rebase) if (rebase == NULL) return; + git_index_free(rebase->index); + git_commit_free(rebase->last_commit); git__free(rebase->onto_name); git__free(rebase->orig_head_name); git__free(rebase->state_path); diff --git a/src/refdb.c b/src/refdb.c index 16fb519a6..debba1276 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -61,12 +61,8 @@ int git_refdb_open(git_refdb **out, git_repository *repo) static void refdb_free_backend(git_refdb *db) { - if (db->backend) { - if (db->backend->free) - db->backend->free(db->backend); - else - git__free(db->backend); - } + if (db->backend) + db->backend->free(db->backend); } int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index e1a77f3ff..f6ed7201a 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -63,6 +63,8 @@ typedef struct refdb_fs_backend { uint32_t direach_flags; } refdb_fs_backend; +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name); + static int packref_cmp(const void *a_, const void *b_) { const struct packref *a = a_, *b = b_; @@ -478,14 +480,16 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) int error = 0; git_buf path = GIT_BUF_INIT; git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; if (!backend->path) /* do nothing if no path for loose refs */ return 0; + fsit_opts.flags = backend->iterator_flags; + if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || - (error = git_iterator_for_filesystem( - &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) { + (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { git_buf_free(&path); return error; } @@ -622,8 +626,9 @@ static int refdb_fs_backend__iterator( iter = git__calloc(1, sizeof(refdb_fs_iter)); GITERR_CHECK_ALLOC(iter); - if (git_pool_init(&iter->pool, 1, 0) < 0 || - git_vector_init(&iter->loose, 8, NULL) < 0) + git_pool_init(&iter->pool, 1); + + if (git_vector_init(&iter->loose, 8, NULL) < 0) goto fail; if (glob != NULL && @@ -712,7 +717,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * assert(file && backend && name); - if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_DEFAULTS)) { + if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name); return GIT_EINVALIDSPEC; } @@ -728,8 +733,11 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE); + if (error == GIT_EDIRECTORY) + giterr_set(GITERR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name); + git_buf_free(&ref_path); - return error; + return error; } static int loose_commit(git_filebuf *file, const git_reference *ref) @@ -1217,6 +1225,11 @@ static int refdb_fs_backend__delete( if ((error = loose_lock(&file, backend, ref_name)) < 0) return error; + if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) { + git_filebuf_cleanup(&file); + return error; + } + return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); } @@ -1404,7 +1417,8 @@ static int setup_namespace(git_buf *path, git_repository *repo) git__free(parts); /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) + if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository, + 0777, GIT_MKDIR_PATH, NULL) < 0) return -1; /* Return root of the namespaced path, i.e. without the trailing '/refs' */ @@ -1453,7 +1467,7 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) entry = git__calloc(1, sizeof(git_reflog_entry)); GITERR_CHECK_ALLOC(entry); - entry->committer = git__malloc(sizeof(git_signature)); + entry->committer = git__calloc(1, sizeof(git_signature)); GITERR_CHECK_ALLOC(entry->committer); if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) @@ -1498,8 +1512,7 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) #undef seek_forward fail: - if (entry) - git_reflog_entry__free(entry); + git_reflog_entry__free(entry); return -1; } @@ -1658,7 +1671,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char repo = backend->repo; - if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_DEFAULTS)) { + if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname); return GIT_EINVALIDSPEC; } @@ -1774,10 +1787,17 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co /* If the new branch matches part of the namespace of a previously deleted branch, * there maybe an obsolete/unused directory (or directory hierarchy) in the way. */ - if (git_path_isdir(git_buf_cstr(&path)) && - (git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { - error = -1; - goto cleanup; + if (git_path_isdir(git_buf_cstr(&path))) { + if ((git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) + error = -1; + else if (git_path_isdir(git_buf_cstr(&path))) { + giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", + ref->name); + error = GIT_EDIRECTORY; + } + + if (error != 0) + goto cleanup; } error = git_futils_writebuffer(&buf, git_buf_cstr(&path), O_WRONLY|O_CREAT|O_APPEND, GIT_REFLOG_FILE_MODE); diff --git a/src/refs.c b/src/refs.c index 7b538659d..a15e31b53 100644 --- a/src/refs.c +++ b/src/refs.c @@ -377,15 +377,9 @@ static int reference__create( return error; if (oid != NULL) { - git_odb *odb; - assert(symbolic == NULL); - /* Sanity check the reference being created - target must exist. */ - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; - - if (!git_odb_exists(odb, oid)) { + if (!git_object__is_valid(repo, oid, GIT_OBJ_ANY)) { giterr_set(GITERR_REFERENCE, "Target OID for the reference doesn't exist on the repository"); return -1; diff --git a/src/refs.h b/src/refs.h index f78ea06b0..fda9532de 100644 --- a/src/refs.h +++ b/src/refs.h @@ -44,6 +44,11 @@ #define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" +#define GIT_SEQUENCER_DIR "sequencer/" +#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head" +#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options" +#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo" + #define GIT_STASH_FILE "stash" #define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE diff --git a/src/refspec.c b/src/refspec.c index f92a6d2b6..debde8692 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -323,8 +323,8 @@ int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { for (j = 0; formatters[j]; j++) { git_buf_clear(&buf); - if (git_buf_printf(&buf, formatters[j], spec->src) < 0) - return -1; + git_buf_printf(&buf, formatters[j], spec->src); + GITERR_CHECK_ALLOC_BUF(&buf); key.name = (char *) git_buf_cstr(&buf); if (!git_vector_search(&pos, refs, &key)) { @@ -348,8 +348,8 @@ int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) git_buf_puts(&buf, GIT_REFS_HEADS_DIR); } - if (git_buf_puts(&buf, spec->dst) < 0) - return -1; + git_buf_puts(&buf, spec->dst); + GITERR_CHECK_ALLOC_BUF(&buf); cur->dst = git_buf_detach(&buf); } diff --git a/src/remote.c b/src/remote.c index f31fc150a..8b7203ee2 100644 --- a/src/remote.c +++ b/src/remote.c @@ -153,7 +153,7 @@ static int get_check_cert(int *out, git_repository *repo) * most specific to least specific. */ /* GIT_SSL_NO_VERIFY environment variable */ - if ((val = getenv("GIT_SSL_NO_VERIFY")) != NULL) + if ((val = p_getenv("GIT_SSL_NO_VERIFY")) != NULL) return git_config_parse_bool(out, val); /* http.sslVerify config setting */ @@ -208,8 +208,8 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n remote->repo = repo; - if (git_vector_init(&remote->refs, 32, NULL) < 0 || - canonicalize_url(&canonical_url, url) < 0) + if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 || + (error = canonicalize_url(&canonical_url, url)) < 0) goto on_error; remote->url = apply_insteadof(repo->_config, canonical_url.ptr, GIT_DIRECTION_FETCH); @@ -687,7 +687,15 @@ int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs) cbs->certificate_check, cbs->payload); } -int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks) +static int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) +{ + if (!t->set_custom_headers) + return 0; + + return t->set_custom_headers(t, custom_headers); +} + +int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_strarray *custom_headers) { git_transport *t; const char *url; @@ -726,6 +734,9 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re if (!t && (error = git_transport_new(&t, remote, url)) < 0) return error; + if ((error = set_transport_custom_headers(t, custom_headers)) != 0) + goto on_error; + if ((error = set_transport_callbacks(t, callbacks)) < 0 || (error = t->connect(t, url, credentials, payload, direction, flags)) != 0) goto on_error; @@ -759,7 +770,7 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur { git_config *cfg; git_config_entry *ce = NULL; - const char *val = NULL; + git_buf val = GIT_BUF_INIT; int error; assert(remote); @@ -789,7 +800,7 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur return error; if (ce && ce->value) { - val = ce->value; + *proxy_url = git__strdup(ce->value); goto found; } } @@ -797,19 +808,28 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur /* http.proxy config setting */ if ((error = git_config__lookup_entry(&ce, cfg, "http.proxy", false)) < 0) return error; + if (ce && ce->value) { - val = ce->value; + *proxy_url = git__strdup(ce->value); goto found; } /* HTTP_PROXY / HTTPS_PROXY environment variables */ - val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY"); + error = git__getenv(&val, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY"); + + if (error < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + return error; + } + + *proxy_url = git_buf_detach(&val); found: - if (val && val[0]) { - *proxy_url = git__strdup(val); - GITERR_CHECK_ALLOC(*proxy_url); - } + GITERR_CHECK_ALLOC(*proxy_url); git_config_entry_free(ce); return 0; @@ -875,16 +895,18 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const size_t i; git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; assert(remote); if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) < 0) goto on_error; if (ls_to_vector(&refs, remote) < 0) @@ -948,16 +970,18 @@ int git_remote_fetch( bool prune = false; git_buf reflog_msg_buf = GIT_BUF_INIT; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; update_fetchhead = opts->update_fetchhead; tagopt = opts->download_tags; } /* Connect and download everything */ - if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs)) != 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) != 0) return error; error = git_remote_download(remote, refspecs, opts); @@ -1325,11 +1349,13 @@ static int update_tips_for_spec( for (; i < refs->length; ++i) { head = git_vector_get(refs, i); autotag = 0; + git_buf_clear(&refname); /* Ignore malformed ref names (which also saves us from tag^{} */ if (!git_reference_is_valid_name(head->name)) continue; + /* If we have a tag, see if the auto-follow rules say to update it */ if (git_refspec_src_matches(&tagspec, head->name)) { if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { @@ -1339,10 +1365,11 @@ static int update_tips_for_spec( git_buf_clear(&refname); if (git_buf_puts(&refname, head->name) < 0) goto on_error; - } else { - continue; } - } else if (git_refspec_src_matches(spec, head->name)) { + } + + /* If we didn't want to auto-follow the tag, check if the refspec matches */ + if (!autotag && git_refspec_src_matches(spec, head->name)) { if (spec->dst) { if (git_refspec_transform(&refname, spec, head->name) < 0) goto on_error; @@ -1356,7 +1383,10 @@ static int update_tips_for_spec( continue; } - } else { + } + + /* If we still don't have a refname, we don't want it */ + if (git_buf_len(&refname) == 0) { continue; } @@ -2362,14 +2392,17 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi git_push *push; git_refspec *spec; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; assert(remote); - if (opts) + if (opts) { cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; + } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) goto cleanup; free_refspecs(&remote->active_refspecs); @@ -2418,15 +2451,17 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ { int error; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; } assert(remote && refspecs); - if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs)) < 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) return error; if ((error = git_remote_upload(remote, refspecs, opts)) < 0) diff --git a/src/repository.c b/src/repository.c index 08f4baa20..8a6fef0f6 100644 --- a/src/repository.c +++ b/src/repository.c @@ -585,7 +585,8 @@ static int load_config( git_repository *repo, const char *global_config_path, const char *xdg_config_path, - const char *system_config_path) + const char *system_config_path, + const char *programdata_path) { int error; git_buf config_path = GIT_BUF_INIT; @@ -626,6 +627,12 @@ static int load_config( 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 && + error != GIT_ENOTFOUND) + goto on_error; + giterr_clear(); /* clear any lingering ENOTFOUND errors */ *out = cfg; @@ -651,11 +658,13 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo) git_buf global_buf = GIT_BUF_INIT; git_buf xdg_buf = GIT_BUF_INIT; git_buf system_buf = GIT_BUF_INIT; + git_buf programdata_buf = GIT_BUF_INIT; git_config *config; git_config_find_global(&global_buf); git_config_find_xdg(&xdg_buf); git_config_find_system(&system_buf); + git_config_find_programdata(&programdata_buf); /* If there is no global file, open a backend for it anyway */ if (git_buf_len(&global_buf) == 0) @@ -665,7 +674,8 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo) &config, repo, path_unless_empty(&global_buf), path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf)); + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); if (!error) { GIT_REFCOUNT_OWN(config, repo); @@ -679,6 +689,7 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo) git_buf_free(&global_buf); git_buf_free(&xdg_buf); git_buf_free(&system_buf); + git_buf_free(&programdata_buf); } *out = repo->_config; @@ -908,12 +919,28 @@ bool git_repository__reserved_names( buf->size = git_repository__reserved_names_win32[i].size; } - /* Try to add any repo-specific reserved names */ + /* Try to add any repo-specific reserved names - the gitlink file + * within a submodule or the repository (if the repository directory + * is beneath the workdir). These are typically `.git`, but should + * be protected in case they are not. Note, repo and workdir paths + * are always prettified to end in `/`, so a prefixcmp is safe. + */ if (!repo->is_bare) { - const char *reserved_path = repo->path_gitlink ? - repo->path_gitlink : repo->path_repository; + int (*prefixcmp)(const char *, const char *); + int error, ignorecase; - if (reserved_names_add8dot3(repo, reserved_path) < 0) + error = git_repository__cvar( + &ignorecase, repo, GIT_CVAR_IGNORECASE); + prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : + git__prefixcmp; + + if (repo->path_gitlink && + reserved_names_add8dot3(repo, repo->path_gitlink) < 0) + goto on_error; + + if (repo->path_repository && + prefixcmp(repo->path_repository, repo->workdir) == 0 && + reserved_names_add8dot3(repo, repo->path_repository) < 0) goto on_error; } } @@ -1279,7 +1306,7 @@ static int repo_write_template( #ifdef GIT_WIN32 if (!error && hidden) { - if (git_win32__sethidden(path.ptr) < 0) + if (git_win32__set_hidden(path.ptr, true) < 0) error = -1; } #else @@ -1373,7 +1400,7 @@ static int repo_init_structure( /* Hide the ".git" directory */ #ifdef GIT_WIN32 if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { - if (git_win32__sethidden(repo_dir) < 0) { + if (git_win32__set_hidden(repo_dir, true) < 0) { giterr_set(GITERR_OS, "Failed to mark Git repository folder as hidden"); return -1; @@ -1411,7 +1438,9 @@ static int repo_init_structure( } if (tdir) { - uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE; + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | + GIT_CPDIR_SIMPLE_TO_MODE | + GIT_CPDIR_COPY_DOTFILES; if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) cpflags |= GIT_CPDIR_CHMOD_DIRS; error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); @@ -1441,8 +1470,8 @@ static int repo_init_structure( if (chmod) mkdir_flags |= GIT_MKDIR_CHMOD; - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, mkdir_flags); + error = git_futils_mkdir_relative( + tpl->path, repo_dir, dmode, mkdir_flags, NULL); } else if (!external_tpl) { const char *content = tpl->content; @@ -1464,7 +1493,7 @@ static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2) * don't try to set gid or grant world write access */ return git_futils_mkdir( - buf->ptr, NULL, mode & ~(S_ISGID | 0002), + buf->ptr, mode & ~(S_ISGID | 0002), GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); } @@ -1568,14 +1597,14 @@ static int repo_init_directories( /* create path #4 */ if (wd_path->size > 0 && (error = git_futils_mkdir( - wd_path->ptr, NULL, dirmode & ~S_ISGID, + wd_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR)) < 0) return error; /* create path #2 (if not the same as #4) */ if (!natural_wd && (error = git_futils_mkdir( - repo_path->ptr, NULL, dirmode & ~S_ISGID, + repo_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) return error; } @@ -1585,7 +1614,7 @@ static int repo_init_directories( has_dotgit) { /* create path #1 */ - error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, + error = git_futils_mkdir(repo_path->ptr, dirmode, GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); } @@ -2195,11 +2224,17 @@ int git_repository_state(git_repository *repo) state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) state = GIT_REPOSITORY_STATE_MERGE; - else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) + else if (git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) { state = GIT_REPOSITORY_STATE_REVERT; - else if(git_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) + if (git_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE; + } + } else if (git_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) { state = GIT_REPOSITORY_STATE_CHERRYPICK; - else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) + if (git_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE; + } + } else if (git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) state = GIT_REPOSITORY_STATE_BISECT; git_buf_free(&repo_path); @@ -2244,6 +2279,7 @@ static const char *state_files[] = { GIT_BISECT_LOG_FILE, GIT_REBASE_MERGE_DIR, GIT_REBASE_APPLY_DIR, + GIT_SEQUENCER_DIR, }; int git_repository_state_cleanup(git_repository *repo) diff --git a/src/reset.c b/src/reset.c index 0ffa51b66..f8a1a1dc8 100644 --- a/src/reset.c +++ b/src/reset.c @@ -145,19 +145,19 @@ static int reset( if ((error = git_buf_printf(&log_message, "reset: moving to %s", to)) < 0) return error; - /* move HEAD to the new target */ - if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, - git_object_id(commit), NULL, git_buf_cstr(&log_message))) < 0) - goto cleanup; - if (reset_type == GIT_RESET_HARD) { - /* overwrite working directory with HEAD */ + /* overwrite working directory with the new tree */ opts.checkout_strategy = GIT_CHECKOUT_FORCE; if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) goto cleanup; } + /* move HEAD to the new target */ + if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, + git_object_id(commit), NULL, git_buf_cstr(&log_message))) < 0) + goto cleanup; + if (reset_type > GIT_RESET_SOFT) { /* reset index to the target content */ diff --git a/src/revwalk.c b/src/revwalk.c index dcdd97915..4815a1089 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -223,8 +223,7 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide) git_buf_joinpath(&buf, GIT_REFS_DIR, glob); else git_buf_puts(&buf, glob); - if (git_buf_oom(&buf)) - return -1; + GITERR_CHECK_ALLOC_BUF(&buf); /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ wildcard = strcspn(glob, "?*["); @@ -535,12 +534,10 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) walk->commits = git_oidmap_alloc(); GITERR_CHECK_ALLOC(walk->commits); - if (git_pqueue_init( - &walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || - git_pool_init(&walk->commit_pool, 1, - git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0) + if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0) return -1; + git_pool_init(&walk->commit_pool, COMMIT_ALLOC); walk->get_next = &revwalk_next_unsorted; walk->enqueue = &revwalk_enqueue_unsorted; diff --git a/src/settings.c b/src/settings.c index 2097ca314..88602bad0 100644 --- a/src/settings.c +++ b/src/settings.c @@ -14,6 +14,7 @@ #include "sysdir.h" #include "cache.h" #include "global.h" +#include "object.h" void git_libgit2_version(int *major, int *minor, int *rev) { @@ -33,6 +34,9 @@ int git_libgit2_features() #endif #if defined(GIT_SSH) | GIT_FEATURE_SSH +#endif +#if defined(GIT_USE_NSEC) + | GIT_FEATURE_NSEC #endif ; } @@ -46,9 +50,18 @@ static int config_level_to_sysdir(int config_level) int val = -1; switch (config_level) { - case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_SYSDIR_SYSTEM; break; - case GIT_CONFIG_LEVEL_XDG: val = GIT_SYSDIR_XDG; break; - case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_SYSDIR_GLOBAL; break; + case GIT_CONFIG_LEVEL_SYSTEM: + val = GIT_SYSDIR_SYSTEM; + break; + case GIT_CONFIG_LEVEL_XDG: + val = GIT_SYSDIR_XDG; + break; + case GIT_CONFIG_LEVEL_GLOBAL: + val = GIT_SYSDIR_GLOBAL; + break; + case GIT_CONFIG_LEVEL_PROGRAMDATA: + val = GIT_SYSDIR_PROGRAMDATA; + break; default: giterr_set( GITERR_INVALID, "Invalid config path selector %d", config_level); @@ -57,6 +70,13 @@ static int config_level_to_sysdir(int config_level) return val; } +extern char *git__user_agent; + +const char *git_libgit2__user_agent() +{ + return git__user_agent; +} + int git_libgit2_opts(int key, ...) { int error = 0; @@ -153,6 +173,23 @@ int git_libgit2_opts(int key, ...) error = -1; #endif break; + case GIT_OPT_SET_USER_AGENT: + git__free(git__user_agent); + git__user_agent = git__strdup(va_arg(ap, const char *)); + if (!git__user_agent) { + giterr_set_oom(); + error = -1; + } + + break; + + case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: + git_object__strict_input_validation = (va_arg(ap, int) != 0); + break; + + default: + giterr_set(GITERR_INVALID, "invalid option key"); + error = -1; } va_end(ap); diff --git a/src/signature.c b/src/signature.c index 818cd300e..d07c93323 100644 --- a/src/signature.c +++ b/src/signature.c @@ -34,13 +34,27 @@ static bool contains_angle_brackets(const char *input) return strchr(input, '<') != NULL || strchr(input, '>') != NULL; } +static bool is_crud(unsigned char c) +{ + return c <= 32 || + c == '.' || + c == ',' || + c == ':' || + c == ';' || + c == '<' || + c == '>' || + c == '"' || + c == '\\' || + c == '\''; +} + static char *extract_trimmed(const char *ptr, size_t len) { - while (len && git__isspace(ptr[0])) { + while (len && is_crud((unsigned char)ptr[0])) { ptr++; len--; } - while (len && git__isspace(ptr[len - 1])) { + while (len && is_crud((unsigned char)ptr[len - 1])) { len--; } @@ -65,10 +79,9 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema GITERR_CHECK_ALLOC(p); p->name = extract_trimmed(name, strlen(name)); + GITERR_CHECK_ALLOC(p->name); p->email = extract_trimmed(email, strlen(email)); - - if (p->name == NULL || p->email == NULL) - return -1; /* oom */ + GITERR_CHECK_ALLOC(p->email); if (p->name[0] == '\0' || p->email[0] == '\0') { git_signature_free(p); diff --git a/src/sortedcache.c b/src/sortedcache.c index 115175724..5c2a167a7 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -20,8 +20,9 @@ int git_sortedcache_new( sc = git__calloc(1, alloclen); GITERR_CHECK_ALLOC(sc); - if (git_pool_init(&sc->pool, 1, 0) < 0 || - git_vector_init(&sc->items, 4, item_cmp) < 0 || + git_pool_init(&sc->pool, 1); + + if (git_vector_init(&sc->items, 4, item_cmp) < 0 || git_strmap_alloc(&sc->map) < 0) goto fail; diff --git a/src/stash.c b/src/stash.c index fcb1112ac..43a464e64 100644 --- a/src/stash.c +++ b/src/stash.c @@ -679,12 +679,14 @@ static int merge_indexes( git_index *theirs_index) { git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&theirs, theirs_index, flags, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) goto done; error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); @@ -704,12 +706,14 @@ static int merge_index_and_tree( git_tree *theirs_tree) { git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&theirs, theirs_tree, flags, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) goto done; error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); @@ -724,7 +728,7 @@ done: static void normalize_apply_options( git_stash_apply_options *opts, const git_stash_apply_options *given_apply_opts) -{ +{ if (given_apply_opts != NULL) { memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); } else { @@ -797,14 +801,15 @@ static int stage_new_files( git_tree *tree) { git_iterator *iterators[2] = { NULL, NULL }; + git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; git_index *index = NULL; int error; if ((error = git_index_new(&index)) < 0 || - (error = git_iterator_for_tree(&iterators[0], parent_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&iterators[1], tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + (error = git_iterator_for_tree( + &iterators[0], parent_tree, &iterator_options)) < 0 || + (error = git_iterator_for_tree( + &iterators[1], tree, &iterator_options)) < 0) goto done; error = git_iterator_walk(iterators, 2, stage_new_file, index); diff --git a/src/stransport_stream.c b/src/stransport_stream.c index 10e19166c..33b6c5c38 100644 --- a/src/stransport_stream.c +++ b/src/stransport_stream.c @@ -108,7 +108,7 @@ int stransport_certificate(git_cert **out, git_stream *stream) return -1; } - st->cert_info.cert_type = GIT_CERT_X509; + st->cert_info.parent.cert_type = GIT_CERT_X509; st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); st->cert_info.len = CFDataGetLength(st->der_data); diff --git a/src/stream.h b/src/stream.h index 43fcc3045..4692c7115 100644 --- a/src/stream.h +++ b/src/stream.h @@ -62,6 +62,9 @@ GIT_INLINE(int) git_stream_close(git_stream *st) GIT_INLINE(void) git_stream_free(git_stream *st) { + if (!st) + return; + st->free(st); } diff --git a/src/submodule.c b/src/submodule.c index 3d028747f..38db41529 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -89,9 +89,11 @@ __KHASH_IMPL( static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); +static git_config *gitmodules_snapshot(git_repository *repo); static int get_url_base(git_buf *url, git_repository *repo); static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo); -static int submodule_load_from_config(const git_config_entry *, void *); +static int submodule_load_each(const git_config_entry *entry, void *payload); +static int submodule_read_config(git_submodule *sm, git_config *cfg); static int submodule_load_from_wd_lite(git_submodule *); static void submodule_get_index_status(unsigned int *, git_submodule *); static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); @@ -144,6 +146,43 @@ static int find_by_path(const git_config_entry *entry, void *payload) return 0; } +/** + * Find out the name of a submodule from its path + */ +static int name_from_path(git_buf *out, git_config *cfg, const char *path) +{ + const char *key = "submodule\\..*\\.path"; + git_config_iterator *iter; + git_config_entry *entry; + int error; + + if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) + return error; + + while ((error = git_config_next(&entry, iter)) == 0) { + const char *fdot, *ldot; + /* TODO: this should maybe be strcasecmp on a case-insensitive fs */ + if (strcmp(path, entry->value) != 0) + continue; + + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + + git_buf_clear(out); + git_buf_put(out, fdot + 1, ldot - fdot - 1); + goto cleanup; + } + + if (error == GIT_ITEROVER) { + giterr_set(GITERR_SUBMODULE, "could not find a submodule name for '%s'", path); + error = GIT_ENOTFOUND; + } + +cleanup: + git_config_iterator_free(iter); + return error; +} + int git_submodule_lookup( git_submodule **out, /* NULL if user only wants to test existence */ git_repository *repo, @@ -190,6 +229,7 @@ int git_submodule_lookup( if (error < 0) { git_submodule_free(sm); + git_buf_free(&path); return error; } @@ -280,19 +320,25 @@ done: return 0; } -static int submodules_from_index(git_strmap *map, git_index *idx) +static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; - if ((error = git_iterator_for_index(&i, idx, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) return error; while (!(error = git_iterator_advance(&entry, i))) { khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -301,7 +347,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx) else sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_index_owner(idx), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_index_entry(sm, entry); git_submodule_free(sm); } @@ -311,24 +357,31 @@ static int submodules_from_index(git_strmap *map, git_index *idx) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; } -static int submodules_from_head(git_strmap *map, git_tree *head) +static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; - if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) return error; while (!(error = git_iterator_advance(&entry, i))) { khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -337,7 +390,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) else sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_tree_owner(head), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_head_data( sm, entry->mode, &entry->id); git_submodule_free(sm); @@ -348,6 +401,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; @@ -355,8 +409,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) /* If have_sm is true, sm is populated, otherwise map an repo are. */ typedef struct { - int have_sm; - git_submodule *sm; + git_config *mods; git_strmap *map; git_repository *repo; } lfc_data; @@ -369,7 +422,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) const char *wd = NULL; git_buf path = GIT_BUF_INIT; git_submodule *sm; - git_config_backend *mods = NULL; + git_config *mods = NULL; uint32_t mask; assert(repo && map); @@ -400,24 +453,28 @@ static int all_submodules(git_repository *repo, git_strmap *map) GIT_SUBMODULE_STATUS__WD_FLAGS | GIT_SUBMODULE_STATUS__WD_OID_VALID; - /* add back submodule information from index */ - if (idx) { - if ((error = submodules_from_index(map, idx)) < 0) - goto cleanup; - } - /* add submodule information from HEAD */ - if (head) { - if ((error = submodules_from_head(map, head)) < 0) - goto cleanup; - } /* add submodule information from .gitmodules */ if (wd) { lfc_data data = { 0 }; data.map = map; data.repo = repo; - if ((mods = open_gitmodules(repo, false)) != NULL && - (error = git_config_file_foreach( - mods, submodule_load_from_config, &data)) < 0) + + if ((mods = gitmodules_snapshot(repo)) == NULL) + goto cleanup; + + data.mods = mods; + if ((error = git_config_foreach( + mods, submodule_load_each, &data)) < 0) + goto cleanup; + } + /* add back submodule information from index */ + if (idx) { + if ((error = submodules_from_index(map, idx, mods)) < 0) + goto cleanup; + } + /* add submodule information from HEAD */ + if (head) { + if ((error = submodules_from_head(map, head, mods)) < 0) goto cleanup; } /* shallow scan submodules in work tree as needed */ @@ -428,7 +485,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) } cleanup: - git_config_file_free(mods); + git_config_free(mods); /* TODO: if we got an error, mark submodule config as invalid? */ git_index_free(idx); git_tree_free(head); @@ -438,7 +495,7 @@ cleanup: int git_submodule_foreach( git_repository *repo, - int (*callback)(git_submodule *sm, const char *name, void *payload), + git_submodule_cb callback, void *payload) { git_vector snapshot = GIT_VECTOR_INIT; @@ -721,9 +778,9 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) goto cleanup; - entry.ctime.seconds = git_commit_time(head); + entry.ctime.seconds = (int32_t)git_commit_time(head); entry.ctime.nanoseconds = 0; - entry.mtime.seconds = git_commit_time(head); + entry.mtime.seconds = (int32_t)git_commit_time(head); entry.mtime.nanoseconds = 0; git_commit_free(head); @@ -787,19 +844,15 @@ int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *ur git_buf_sanitize(out); + /* We do this in all platforms in case someone on Windows created the .gitmodules */ if (strchr(url, '\\')) { - char *p; - if ((error = git_buf_puts(&normalized, url)) < 0) + if ((error = git_path_normalize_slashes(&normalized, url)) < 0) return error; - for (p = normalized.ptr; *p; p++) { - if (*p == '\\') - *p = '/'; - } - url = normalized.ptr; } + if (git_path_is_relative(url)) { if (!(error = get_url_base(out, repo))) error = git_path_apply_relative(out, url); @@ -984,7 +1037,7 @@ static int submodule_repo_create( /** * Repodir: path to the sub-repo. sub-repo goes in: - * /modules// with a gitlink in the + * /modules// with a gitlink in the * sub-repo workdir directory to that repository. */ error = git_buf_join3( @@ -1101,7 +1154,7 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio clone_options.repository_cb_payload = sm; /* - * Do not perform checkout as part of clone, instead we + * Do not perform checkout as part of clone, instead we * will checkout the specific commit manually. */ clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; @@ -1370,60 +1423,39 @@ static int submodule_update_head(git_submodule *submodule) return 0; } - int git_submodule_reload(git_submodule *sm, int force) { int error = 0; - git_config_backend *mods; - lfc_data data = { 0 }; + git_config *mods; GIT_UNUSED(force); assert(sm); - /* refresh index data */ - if ((error = submodule_update_index(sm)) < 0) - return error; + if (!git_repository_is_bare(sm->repo)) { + /* refresh config data */ + mods = gitmodules_snapshot(sm->repo); + if (mods != NULL) { + error = submodule_read_config(sm, mods); + git_config_free(mods); - /* refresh HEAD tree data */ - if ((error = submodule_update_head(sm)) < 0) - return error; - - /* done if bare */ - if (git_repository_is_bare(sm->repo)) - return error; - - /* refresh config data */ - mods = open_gitmodules(sm->repo, GITMODULES_EXISTING); - if (mods != NULL) { - git_buf path = GIT_BUF_INIT; - - git_buf_sets(&path, "submodule\\."); - git_buf_text_puts_escape_regex(&path, sm->name); - git_buf_puts(&path, "\\..*"); - - if (git_buf_oom(&path)) { - error = -1; - } else { - data.have_sm = 1; - data.sm = sm; - error = git_config_file_foreach_match( - mods, path.ptr, submodule_load_from_config, &data); + if (error < 0) + return error; } - git_buf_free(&path); - git_config_file_free(mods); + /* refresh wd data */ + sm->flags &= + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_FLAGS); - if (error < 0) - return error; + error = submodule_load_from_wd_lite(sm); } - /* refresh wd data */ - sm->flags &= - ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_FLAGS); + if (error == 0 && (error = submodule_update_index(sm)) == 0) + error = submodule_update_head(sm); - return submodule_load_from_wd_lite(sm); + return error; } static void submodule_copy_oid_maybe( @@ -1631,134 +1663,149 @@ int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) return 0; } -static int submodule_load_from_config( - const git_config_entry *entry, void *payload) +static int get_value(const char **out, git_config *cfg, git_buf *buf, const char *name, const char *field) { - const char *namestart, *property; - const char *key = entry->name, *value = entry->value, *path; - char *alternate = NULL, *replaced = NULL; - git_buf name = GIT_BUF_INIT; - lfc_data *data = payload; - git_submodule *sm; - int error = 0; + int error; - if (git__prefixcmp(key, "submodule.") != 0) + git_buf_clear(buf); + + if ((error = git_buf_printf(buf, "submodule.%s.%s", name, field)) < 0 || + (error = git_config_get_string(out, cfg, buf->ptr)) < 0) + return error; + + return error; +} + +static int submodule_read_config(git_submodule *sm, git_config *cfg) +{ + git_buf key = GIT_BUF_INIT; + const char *value; + int error, in_config = 0; + + /* + * TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) + */ + + if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { + in_config = 1; + /* + * TODO: if case insensitive filesystem, then the following strcmp + * should be strcasecmp + */ + if (strcmp(sm->name, value) != 0) { + if (sm->path != sm->name) + git__free(sm->path); + sm->path = git__strdup(value); + GITERR_CHECK_ALLOC(sm->path); + } + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { + in_config = 1; + sm->url = git__strdup(value); + GITERR_CHECK_ALLOC(sm->url); + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { + in_config = 1; + sm->branch = git__strdup(value); + GITERR_CHECK_ALLOC(sm->branch); + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_update(&sm->update, value)) < 0) + goto cleanup; + sm->update_default = sm->update; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) + goto cleanup; + sm->fetch_recurse_default = sm->fetch_recurse; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) + goto cleanup; + sm->ignore_default = sm->ignore; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if (in_config) + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + + error = 0; + +cleanup: + git_buf_free(&key); + return error; +} + +static int submodule_load_each(const git_config_entry *entry, void *payload) +{ + lfc_data *data = payload; + const char *namestart, *property; + git_strmap_iter pos; + git_strmap *map = data->map; + git_buf name = GIT_BUF_INIT; + git_submodule *sm; + int error; + + if (git__prefixcmp(entry->name, "submodule.") != 0) return 0; - namestart = key + strlen("submodule."); + namestart = entry->name + strlen("submodule."); property = strrchr(namestart, '.'); if (!property || (property == namestart)) return 0; property++; - path = !strcasecmp(property, "path") ? value : NULL; if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) + return error; + + /* + * Now that we have the submodule's name, we can use that to + * figure out whether it's in the map. If it's not, we create + * a new submodule, load the config and insert it. If it's + * already inserted, we've already loaded it, so we skip. + */ + pos = git_strmap_lookup_index(map, name.ptr); + if (git_strmap_valid_index(map, pos)) { + error = 0; + goto done; + } + + if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) goto done; - if (data->have_sm) { - sm = data->sm; - } else { - khiter_t pos; - git_strmap *map = data->map; - pos = git_strmap_lookup_index(map, path ? path : name.ptr); - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); - } else { - if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) - goto done; - - git_strmap_insert(map, sm->name, sm, error); - assert(error != 0); - if (error < 0) - goto done; - error = 0; - } + if ((error = submodule_read_config(sm, data->mods)) < 0) { + git_submodule_free(sm); + goto done; } - sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; - - /* Only from config might we get differing names & paths. If so, then - * update the submodule and insert under the alternative key. - */ - - /* TODO: if case insensitive filesystem, then the following strcmps - * should be strcasecmp - */ - - if (strcmp(sm->name, name.ptr) != 0) { /* name changed */ - if (sm->path && !strcmp(sm->path, name.ptr)) { /* already set as path */ - replaced = sm->name; - sm->name = sm->path; - } else { - if (sm->name != sm->path) - replaced = sm->name; - alternate = sm->name = git_buf_detach(&name); - } - } - else if (path && strcmp(path, sm->path) != 0) { /* path changed */ - if (!strcmp(sm->name, value)) { /* already set as name */ - replaced = sm->path; - sm->path = sm->name; - } else { - if (sm->path != sm->name) - replaced = sm->path; - if ((alternate = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - sm->path = alternate; - } - } - - /* Deregister under name being replaced */ - if (replaced) { - git__free(replaced); - } - - /* TODO: Look up path in index and if it is present but not a GITLINK - * then this should be deleted (at least to match git's behavior) - */ - - if (path) + git_strmap_insert(map, sm->name, sm, error); + assert(error != 0); + if (error < 0) goto done; - /* copy other properties into submodule entry */ - if (strcasecmp(property, "url") == 0) { - git__free(sm->url); - sm->url = NULL; - - if (value != NULL && (sm->url = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - } - else if (strcasecmp(property, "branch") == 0) { - git__free(sm->branch); - sm->branch = NULL; - - if (value != NULL && (sm->branch = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - } - else if (strcasecmp(property, "update") == 0) { - if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - goto done; - sm->update_default = sm->update; - } - else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { - if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - goto done; - sm->fetch_recurse_default = sm->fetch_recurse; - } - else if (strcasecmp(property, "ignore") == 0) { - if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - goto done; - sm->ignore_default = sm->ignore; - } - /* ignore other unknown submodule properties */ + error = 0; done: git_buf_free(&name); @@ -1782,6 +1829,35 @@ static int submodule_load_from_wd_lite(git_submodule *sm) return 0; } +/** + * Returns a snapshot of $WORK_TREE/.gitmodules. + * + * We ignore any errors and just pretend the file isn't there. + */ +static git_config *gitmodules_snapshot(git_repository *repo) +{ + const char *workdir = git_repository_workdir(repo); + git_config *mods = NULL, *snap = NULL; + git_buf path = GIT_BUF_INIT; + + if (workdir != NULL) { + if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) + return NULL; + + if (git_config_open_ondisk(&mods, path.ptr) < 0) + mods = NULL; + } + + git_buf_free(&path); + + if (mods) { + git_config_snapshot(&snap, mods); + git_config_free(mods); + } + + return snap; +} + static git_config_backend *open_gitmodules( git_repository *repo, int okay_to_create) diff --git a/src/sysdir.c b/src/sysdir.c index cd94a8b57..bf53d830f 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -15,6 +15,16 @@ #include "win32/findfile.h" #endif +static int git_sysdir_guess_programdata_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_programdata_dirs(out); +#else + git_buf_clear(out); + return 0; +#endif +} + static int git_sysdir_guess_system_dirs(git_buf *out) { #ifdef GIT_WIN32 @@ -29,7 +39,14 @@ static int git_sysdir_guess_global_dirs(git_buf *out) #ifdef GIT_WIN32 return git_win32__find_global_dirs(out); #else - return git_buf_sets(out, getenv("HOME")); + int error = git__getenv(out, "HOME"); + + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + return error; #endif } @@ -38,15 +55,22 @@ static int git_sysdir_guess_xdg_dirs(git_buf *out) #ifdef GIT_WIN32 return git_win32__find_xdg_dirs(out); #else - const char *env = NULL; + git_buf env = GIT_BUF_INIT; + int error; - if ((env = getenv("XDG_CONFIG_HOME")) != NULL) - return git_buf_joinpath(out, env, "git"); - else if ((env = getenv("HOME")) != NULL) - return git_buf_joinpath(out, env, ".config/git"); + if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) + error = git_buf_joinpath(out, env.ptr, "git"); - git_buf_clear(out); - return 0; + if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) + error = git_buf_joinpath(out, env.ptr, ".config/git"); + + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + git_buf_free(&env); + return error; #endif } @@ -62,12 +86,13 @@ static int git_sysdir_guess_template_dirs(git_buf *out) typedef int (*git_sysdir_guess_cb)(git_buf *out); static git_buf git_sysdir__dirs[GIT_SYSDIR__MAX] = - { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; + { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; static git_sysdir_guess_cb git_sysdir__dir_guess[GIT_SYSDIR__MAX] = { git_sysdir_guess_system_dirs, git_sysdir_guess_global_dirs, git_sysdir_guess_xdg_dirs, + git_sysdir_guess_programdata_dirs, git_sysdir_guess_template_dirs, }; @@ -244,6 +269,12 @@ int git_sysdir_find_xdg_file(git_buf *path, const char *filename) path, filename, GIT_SYSDIR_XDG, "global/xdg"); } +int git_sysdir_find_programdata_file(git_buf *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData"); +} + int git_sysdir_find_template_dir(git_buf *path) { return git_sysdir_find_in_dirlist( diff --git a/src/sysdir.h b/src/sysdir.h index f1bbf0bae..12874fc85 100644 --- a/src/sysdir.h +++ b/src/sysdir.h @@ -38,6 +38,15 @@ extern int git_sysdir_find_xdg_file(git_buf *path, const char *filename); */ extern int git_sysdir_find_system_file(git_buf *path, const char *filename); +/** + * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%) + * + * @param path buffer to write the full path into + * @param filename name of file to find in the ProgramData directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_programdata_file(git_buf *path, const char *filename); + /** * Find template directory. * @@ -50,8 +59,9 @@ typedef enum { GIT_SYSDIR_SYSTEM = 0, GIT_SYSDIR_GLOBAL = 1, GIT_SYSDIR_XDG = 2, - GIT_SYSDIR_TEMPLATE = 3, - GIT_SYSDIR__MAX = 4, + GIT_SYSDIR_PROGRAMDATA = 3, + GIT_SYSDIR_TEMPLATE = 4, + GIT_SYSDIR__MAX = 5, } git_sysdir_t; /** diff --git a/src/tag.c b/src/tag.c index 6e69d760d..c4bce1f22 100644 --- a/src/tag.c +++ b/src/tag.c @@ -358,7 +358,7 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu git_odb_object_free(target_obj); /** Ensure the tag name doesn't conflict with an already existing - * reference unless overwriting has explictly been requested **/ + * reference unless overwriting has explicitly been requested **/ if (error == 0 && !allow_ref_overwrite) { giterr_set(GITERR_TAG, "Tag already exists"); return GIT_EEXISTS; diff --git a/src/thread-utils.h b/src/thread-utils.h index dd1136caf..14c8a41ff 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -275,7 +275,7 @@ GIT_INLINE(int) git_atomic_get(git_atomic *a) extern int git_online_cpus(void); -#if defined(GIT_THREADS) && defined(GIT_WIN32) +#if defined(GIT_THREADS) && defined(_MSC_VER) # define GIT_MEMORY_BARRIER MemoryBarrier() #elif defined(GIT_THREADS) # define GIT_MEMORY_BARRIER __sync_synchronize() diff --git a/src/tls_stream.c b/src/tls_stream.c index 39a8ce343..83e2d064a 100644 --- a/src/tls_stream.c +++ b/src/tls_stream.c @@ -11,8 +11,21 @@ #include "openssl_stream.h" #include "stransport_stream.h" +static git_stream_cb tls_ctor; + +int git_stream_register_tls(git_stream_cb ctor) +{ + tls_ctor = ctor; + + return 0; +} + int git_tls_stream_new(git_stream **out, const char *host, const char *port) { + + if (tls_ctor) + return tls_ctor(out, host, port); + #ifdef GIT_SECURE_TRANSPORT return git_stransport_stream_new(out, host, port); #elif defined(GIT_OPENSSL) diff --git a/src/transaction.c b/src/transaction.c index e8331891c..2c8a1e8bd 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -12,6 +12,7 @@ #include "pool.h" #include "reflog.h" #include "signature.h" +#include "config.h" #include "git2/transaction.h" #include "git2/signature.h" @@ -20,6 +21,12 @@ GIT__USE_STRMAP +typedef enum { + TRANSACTION_NONE, + TRANSACTION_REFS, + TRANSACTION_CONFIG, +} transaction_t; + typedef struct { const char *name; void *payload; @@ -39,13 +46,29 @@ typedef struct { } transaction_node; struct git_transaction { + transaction_t type; git_repository *repo; git_refdb *db; + git_config *cfg; git_strmap *locks; git_pool pool; }; +int git_transaction_config_new(git_transaction **out, git_config *cfg) +{ + git_transaction *tx; + assert(out && cfg); + + tx = git__calloc(1, sizeof(git_transaction)); + GITERR_CHECK_ALLOC(tx); + + tx->type = TRANSACTION_CONFIG; + tx->cfg = cfg; + *out = tx; + return 0; +} + int git_transaction_new(git_transaction **out, git_repository *repo) { int error; @@ -54,8 +77,7 @@ int git_transaction_new(git_transaction **out, git_repository *repo) assert(out && repo); - if ((error = git_pool_init(&pool, 1, 0)) < 0) - return error; + git_pool_init(&pool, 1); tx = git_pool_mallocz(&pool, sizeof(git_transaction)); if (!tx) { @@ -71,6 +93,7 @@ int git_transaction_new(git_transaction **out, git_repository *repo) if ((error = git_repository_refdb(&tx->db, repo)) < 0) goto on_error; + tx->type = TRANSACTION_REFS; memcpy(&tx->pool, &pool, sizeof(git_pool)); tx->repo = repo; *out = tx; @@ -305,6 +328,13 @@ int git_transaction_commit(git_transaction *tx) assert(tx); + if (tx->type == TRANSACTION_CONFIG) { + error = git_config_unlock(tx->cfg, true); + tx->cfg = NULL; + + return error; + } + for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { if (!git_strmap_has_data(tx->locks, pos)) continue; @@ -332,6 +362,16 @@ void git_transaction_free(git_transaction *tx) assert(tx); + if (tx->type == TRANSACTION_CONFIG) { + if (tx->cfg) { + git_config_unlock(tx->cfg, false); + git_config_free(tx->cfg); + } + + git__free(tx); + return; + } + /* start by unlocking the ones we've left hanging, if any */ for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { if (!git_strmap_has_data(tx->locks, pos)) diff --git a/src/transaction.h b/src/transaction.h new file mode 100644 index 000000000..780c06830 --- /dev/null +++ b/src/transaction.h @@ -0,0 +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. + */ +#ifndef INCLUDE_transaction_h__ +#define INCLUDE_transaction_h__ + +#include "common.h" + +int git_transaction_config_new(git_transaction **out, git_config *cfg); + +#endif diff --git a/src/transports/cred.c b/src/transports/cred.c index 044b2a262..49ede48bf 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -378,3 +378,11 @@ int git_cred_username_new(git_cred **cred, const char *username) *cred = (git_cred *) c; return 0; } + +void git_cred_free(git_cred *cred) +{ + if (!cred) + return; + + cred->free(cred); +} diff --git a/src/transports/git.c b/src/transports/git.c index 52de92d09..6c6acf9c5 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -130,11 +130,14 @@ static int git_proto_stream_write( static void git_proto_stream_free(git_smart_subtransport_stream *stream) { - git_proto_stream *s = (git_proto_stream *)stream; - git_subtransport *t = OWNING_SUBTRANSPORT(s); - int ret; + git_proto_stream *s; + git_subtransport *t; - GIT_UNUSED(ret); + if (!stream) + return; + + s = (git_proto_stream *)stream; + t = OWNING_SUBTRANSPORT(s); t->current_stream = NULL; diff --git a/src/transports/http.c b/src/transports/http.c index e3d90de11..88b124bf7 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -10,6 +10,7 @@ #include "http_parser.h" #include "buffer.h" #include "netops.h" +#include "global.h" #include "remote.h" #include "smart.h" #include "auth.h" @@ -36,6 +37,8 @@ static const char *post_verb = "POST"; #define PARSE_ERROR_GENERIC -1 #define PARSE_ERROR_REPLAY -2 +/** Look at the user field */ +#define PARSE_ERROR_EXT -3 #define CHUNK_SIZE 4096 @@ -78,6 +81,7 @@ typedef struct { git_vector www_authenticate; enum last_cb last_cb; int parse_error; + int error; unsigned parse_finished : 1; /* Authentication */ @@ -183,6 +187,16 @@ 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, @@ -190,10 +204,11 @@ static int gen_request( { http_subtransport *t = OWNING_SUBTRANSPORT(s); const char *path = t->connection_data.path ? t->connection_data.path : "/"; + size_t i; git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); - git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); + git_buf_printf(buf, "User-Agent: git/1.0 (%s)\r\n", user_agent()); git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host); if (s->chunked || content_length > 0) { @@ -207,6 +222,11 @@ static int gen_request( } else git_buf_puts(buf, "Accept: */*\r\n"); + for (i = 0; i < t->owner->custom_headers.count; i++) { + if (t->owner->custom_headers.strings[i]) + git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]); + } + /* Apply credentials to the request */ if (apply_credentials(buf, t) < 0) return -1; @@ -351,7 +371,8 @@ static int on_headers_complete(http_parser *parser) if (error == GIT_PASSTHROUGH) { no_callback = 1; } else if (error < 0) { - return PARSE_ERROR_GENERIC; + t->error = error; + return t->parse_error = PARSE_ERROR_EXT; } else { assert(t->cred); @@ -712,6 +733,10 @@ replay: goto replay; } + if (t->parse_error == PARSE_ERROR_EXT) { + return t->error; + } + if (t->parse_error < 0) return -1; diff --git a/src/transports/smart.c b/src/transports/smart.c index 85a49e543..b0611c35e 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,6 +66,84 @@ static int git_smart__set_callbacks( return 0; } +static int http_header_name_length(const char *http_header) +{ + const char *colon = strchr(http_header, ':'); + if (!colon) + return 0; + return colon - http_header; +} + +static bool is_malformed_http_header(const char *http_header) +{ + const char *c; + int name_len; + + // Disallow \r and \n + c = strchr(http_header, '\r'); + if (c) + return true; + c = strchr(http_header, '\n'); + if (c) + return true; + + // Require a header name followed by : + name_len = http_header_name_length(http_header); + if (name_len < 1) + return true; + + return false; +} + +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; + +static bool is_forbidden_custom_header(const char *custom_header) +{ + unsigned long i; + int name_len = http_header_name_length(custom_header); + + // Disallow headers that we set + for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return true; + + return false; +} + +static int git_smart__set_custom_headers( + git_transport *transport, + const git_strarray *custom_headers) +{ + transport_smart *t = (transport_smart *)transport; + size_t i; + + if (t->custom_headers.count) + git_strarray_free(&t->custom_headers); + + if (!custom_headers) + return 0; + + for (i = 0; i < custom_headers->count; i++) { + if (is_malformed_http_header(custom_headers->strings[i])) { + giterr_set(GITERR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); + return -1; + } + if (is_forbidden_custom_header(custom_headers->strings[i])) { + giterr_set(GITERR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); + return -1; + } + } + + return git_strarray_copy(&t->custom_headers, custom_headers); +} + int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; @@ -362,6 +440,8 @@ static void git_smart__free(git_transport *transport) git_vector_free(refs); + git_strarray_free(&t->custom_headers); + git__free(t); } @@ -372,6 +452,20 @@ static int ref_name_cmp(const void *a, const void *b) return strcmp(ref_a->head.name, ref_b->head.name); } +int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) +{ + transport_smart *t = (transport_smart *)transport; + + return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload); +} + +int git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods) +{ + transport_smart *t = (transport_smart *)transport; + + return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload); +} + int git_transport_smart(git_transport **out, git_remote *owner, void *param) { transport_smart *t; @@ -385,6 +479,7 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) t->parent.version = GIT_TRANSPORT_VERSION; t->parent.set_callbacks = git_smart__set_callbacks; + t->parent.set_custom_headers = git_smart__set_custom_headers; t->parent.connect = git_smart__connect; t->parent.close = git_smart__close; t->parent.free = git_smart__free; diff --git a/src/transports/smart.h b/src/transports/smart.h index 4c728c7cc..800466adf 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -139,6 +139,7 @@ typedef struct { git_transport_message_cb error_cb; git_transport_certificate_check_cb certificate_check_cb; void *message_cb_payload; + git_strarray custom_headers; git_smart_subtransport *wrapped; git_smart_subtransport_stream *current_stream; transport_smart_caps caps; diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 9ccbd8085..2ea57bb64 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -271,6 +271,7 @@ static int ok_pkt(git_pkt **out, const char *line, size_t len) line += 3; /* skip "ok " */ if (!(ptr = strchr(line, '\n'))) { giterr_set(GITERR_NET, "Invalid packet line"); + git__free(pkt); return -1; } len = ptr - line; @@ -295,13 +296,12 @@ static int ng_pkt(git_pkt **out, const char *line, size_t len) pkt = git__malloc(sizeof(*pkt)); GITERR_CHECK_ALLOC(pkt); + pkt->ref = NULL; pkt->type = GIT_PKT_NG; line += 3; /* skip "ng " */ - if (!(ptr = strchr(line, ' '))) { - giterr_set(GITERR_NET, "Invalid packet line"); - return -1; - } + if (!(ptr = strchr(line, ' '))) + goto out_err; len = ptr - line; GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1); @@ -312,10 +312,8 @@ static int ng_pkt(git_pkt **out, const char *line, size_t len) pkt->ref[len] = '\0'; line = ptr + 1; - if (!(ptr = strchr(line, '\n'))) { - giterr_set(GITERR_NET, "Invalid packet line"); - return -1; - } + if (!(ptr = strchr(line, '\n'))) + goto out_err; len = ptr - line; GITERR_CHECK_ALLOC_ADD(&alloclen, len, 1); @@ -327,6 +325,12 @@ static int ng_pkt(git_pkt **out, const char *line, size_t len) *out = (git_pkt *)pkt; return 0; + +out_err: + giterr_set(GITERR_NET, "Invalid packet line"); + git__free(pkt->ref); + git__free(pkt); + return -1; } static int unpack_pkt(git_pkt **out, const char *line, size_t len) @@ -351,7 +355,7 @@ static int unpack_pkt(git_pkt **out, const char *line, size_t len) static int32_t parse_len(const char *line) { char num[PKT_LEN_SIZE + 1]; - int i, error; + int i, k, error; int32_t len; const char *num_end; @@ -360,7 +364,14 @@ static int32_t parse_len(const char *line) for (i = 0; i < PKT_LEN_SIZE; ++i) { if (!isxdigit(num[i])) { - giterr_set(GITERR_NET, "Found invalid hex digit in length"); + /* Make sure there are no special characters before passing to error message */ + for (k = 0; k < PKT_LEN_SIZE; ++k) { + if(!isprint(num[k])) { + num[k] = '.'; + } + } + + giterr_set(GITERR_NET, "invalid hex digit in length: '%s'", num); return -1; } } @@ -533,7 +544,9 @@ static int buffer_want_with_caps(const git_remote_head *head, transport_smart_ca "%04xwant %s %s\n", (unsigned int)len, oid, git_buf_cstr(&str)); git_buf_free(&str); - return git_buf_oom(buf); + GITERR_CHECK_ALLOC_BUF(buf); + + return 0; } /* diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 0920f2eef..6363378ec 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -108,6 +108,7 @@ static int append_symref(const char **out, git_vector *symrefs, const char *ptr) if (giterr_last()->klass != GITERR_NOMEMORY) goto on_invalid; + git__free(mapping); return error; } @@ -120,6 +121,7 @@ static int append_symref(const char **out, git_vector *symrefs, const char *ptr) on_invalid: giterr_set(GITERR_NET, "remote sent invalid symref"); git_refspec__free(mapping); + git__free(mapping); return -1; } @@ -957,7 +959,7 @@ int git_smart__push(git_transport *transport, git_push *push, const git_remote_c packbuilder_payload.pb = push->pb; - if (cbs && cbs->transfer_progress) { + if (cbs && cbs->push_transfer_progress) { packbuilder_payload.cb = cbs->push_transfer_progress; packbuilder_payload.cb_payload = cbs->payload; } diff --git a/src/transports/ssh.c b/src/transports/ssh.c index e3792ebb3..35739abe3 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -15,6 +15,7 @@ #include "smart.h" #include "cred.h" #include "socket_stream.h" +#include "ssh.h" #ifdef GIT_SSH @@ -136,9 +137,14 @@ static int ssh_stream_read( * not-found error, so read from stderr and signal EOF on * stderr. */ - if (rc == 0 && (rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { - giterr_set(GITERR_SSH, "%*s", rc, buffer); - return GIT_EEOF; + if (rc == 0) { + if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { + giterr_set(GITERR_SSH, "%*s", rc, buffer); + return GIT_EEOF; + } else if (rc < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read stderr"); + return -1; + } } @@ -527,10 +533,10 @@ static int _git_ssh_setup_conn( goto done; if (t->owner->certificate_check_cb != NULL) { - git_cert_hostkey cert = { 0 }, *cert_ptr; + git_cert_hostkey cert = {{ 0 }}, *cert_ptr; const char *key; - cert.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; + cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); if (key != NULL) { @@ -756,8 +762,10 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use list = libssh2_userauth_list(session, username, strlen(username)); /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ - if (list == NULL && !libssh2_userauth_authenticated(session)) + if (list == NULL && !libssh2_userauth_authenticated(session)) { + ssh_error(session, "Failed to retrieve list of SSH authentication methods"); return -1; + } ptr = list; while (ptr) { @@ -869,3 +877,18 @@ int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *p return -1; #endif } + +int git_transport_ssh_global_init(void) +{ +#ifdef GIT_SSH + + libssh2_init(0); + return 0; + +#else + + /* Nothing to initialize */ + return 0; + +#endif +} diff --git a/src/merge_file.h b/src/transports/ssh.h similarity index 67% rename from src/merge_file.h rename to src/transports/ssh.h index 263391ee3..2db2cc5df 100644 --- a/src/merge_file.h +++ b/src/transports/ssh.h @@ -4,11 +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. */ -#ifndef INCLUDE_filediff_h__ -#define INCLUDE_filediff_h__ +#ifndef INCLUDE_ssh_h__ +#define INCLUDE_ssh_h__ -#include "xdiff/xdiff.h" - -#include "git2/merge.h" +int git_transport_ssh_global_init(void); #endif diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index da047d690..32b838084 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -15,6 +15,7 @@ #include "smart.h" #include "remote.h" #include "repository.h" +#include "global.h" #include #include @@ -52,10 +53,15 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; #if defined(__MINGW32__) -const CLSID CLSID_InternetSecurityManager = { 0x7B8A2D94, 0x0AC9, 0x11D1, +static const CLSID CLSID_InternetSecurityManager_mingw = + { 0x7B8A2D94, 0x0AC9, 0x11D1, { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } }; -const IID IID_IInternetSecurityManager = { 0x79EAC9EE, 0xBAF9, 0x11CE, +static const IID IID_IInternetSecurityManager_mingw = + { 0x79EAC9EE, 0xBAF9, 0x11CE, { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } }; + +# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw +# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw #endif #define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) @@ -228,7 +234,7 @@ static int certificate_check(winhttp_stream *s, int valid) } giterr_clear(); - cert.cert_type = GIT_CERT_X509; + 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); @@ -277,6 +283,7 @@ static int winhttp_stream_connect(winhttp_stream *s) unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + size_t i; /* Prepare URL */ git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); @@ -409,6 +416,23 @@ static int winhttp_stream_connect(winhttp_stream *s) } } + for (i = 0; i < t->owner->custom_headers.count; i++) { + if (t->owner->custom_headers.strings[i]) { + git_buf_clear(&buf); + git_buf_puts(&buf, t->owner->custom_headers.strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + } + } + /* If requested, disable certificate validation */ if (t->connection_data.use_ssl) { int flags; @@ -549,12 +573,28 @@ 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 int winhttp_connect( winhttp_subtransport *t) { - wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; wchar_t *wide_host; int32_t port; + wchar_t *wide_ua; + git_buf ua = GIT_BUF_INIT; int error = -1; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; @@ -572,9 +612,23 @@ static int winhttp_connect( return -1; } + if ((error = user_agent(&ua)) < 0) { + git__free(wide_host); + return error; + } + + if (git__utf8_to_16_alloc(&wide_ua, git_buf_cstr(&ua)) < 0) { + giterr_set(GITERR_OS, "Unable to convert host to wide characters"); + git__free(wide_host); + git_buf_free(&ua); + return -1; + } + + git_buf_free(&ua); + /* Establish session */ t->session = WinHttpOpen( - ua, + wide_ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, @@ -610,6 +664,7 @@ on_error: winhttp_close_connection(t); git__free(wide_host); + git__free(wide_ua); return error; } @@ -871,16 +926,20 @@ replay: if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0) return -1; - if (allowed_types && - (!t->cred || 0 == (t->cred->credtype & allowed_types))) { + if (allowed_types) { int cred_error = 1; + git_cred_free(t->cred); + t->cred = NULL; /* Start with the user-supplied credential callback, if present */ if (t->owner->cred_acquire_cb) { cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->connection_data.user, allowed_types, t->owner->cred_acquire_payload); - if (cred_error < 0) + /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */ + if (cred_error == GIT_PASSTHROUGH) + cred_error = 1; + else if (cred_error < 0) return cred_error; } diff --git a/src/tree.c b/src/tree.c index bdd17661b..48b9f121d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -17,6 +17,9 @@ #define DEFAULT_TREE_SIZE 16 #define MAX_FILEMODE_BYTES 6 +#define TREE_ENTRY_CHECK_NAMELEN(n) \ + if (n > UINT16_MAX) { giterr_set(GITERR_INVALID, "tree entry path too long"); } + GIT__USE_STRMAP static bool valid_filemode(const int filemode) @@ -81,27 +84,58 @@ int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2) git__strncasecmp); } -static git_tree_entry *alloc_entry(const char *filename) +/** + * Allocate either from the pool or from the system allocator + */ +static git_tree_entry *alloc_entry_base(git_pool *pool, const char *filename, size_t filename_len) { git_tree_entry *entry = NULL; - size_t filename_len = strlen(filename), tree_len; + size_t tree_len; + + TREE_ENTRY_CHECK_NAMELEN(filename_len); if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || - !(entry = git__malloc(tree_len))) + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1)) + return NULL; + + entry = pool ? git_pool_malloc(pool, tree_len) : + git__malloc(tree_len); + if (!entry) return NULL; memset(entry, 0x0, sizeof(git_tree_entry)); memcpy(entry->filename, filename, filename_len); entry->filename[filename_len] = 0; - entry->filename_len = filename_len; + entry->filename_len = (uint16_t)filename_len; return entry; } +/** + * Allocate a tree entry, using the poolin the tree which owns + * it. This is useful when reading trees, so we don't allocate a ton + * of small strings but can use the pool. + */ +static git_tree_entry *alloc_entry_pooled(git_pool *pool, const char *filename, size_t filename_len) +{ + git_tree_entry *entry = NULL; + + if (!(entry = alloc_entry_base(pool, filename, filename_len))) + return NULL; + + entry->pooled = true; + + return entry; +} + +static git_tree_entry *alloc_entry(const char *filename) +{ + return alloc_entry_base(NULL, filename, strlen(filename)); +} + struct tree_key_search { const char *filename; - size_t filename_len; + uint16_t filename_len; }; static int homing_search_cmp(const void *key, const void *array_member) @@ -109,8 +143,8 @@ static int homing_search_cmp(const void *key, const void *array_member) const struct tree_key_search *ksearch = key; const git_tree_entry *entry = array_member; - const size_t len1 = ksearch->filename_len; - const size_t len2 = entry->filename_len; + const uint16_t len1 = ksearch->filename_len; + const uint16_t len2 = entry->filename_len; return memcmp( ksearch->filename, @@ -146,8 +180,10 @@ static int tree_key_search( const git_tree_entry *entry; size_t homing, i; + TREE_ENTRY_CHECK_NAMELEN(filename_len); + ksearch.filename = filename; - ksearch.filename_len = filename_len; + ksearch.filename_len = (uint16_t)filename_len; /* Initial homing search; find an entry on the tree with * the same prefix as the filename we're looking for */ @@ -198,7 +234,7 @@ static int tree_key_search( void git_tree_entry_free(git_tree_entry *entry) { - if (entry == NULL) + if (entry == NULL || entry->pooled) return; git__free(entry); @@ -219,6 +255,8 @@ int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) memcpy(copy, source, total_size); + copy->pooled = 0; + *dest = copy; return 0; } @@ -233,6 +271,7 @@ void git_tree__free(void *_tree) git_tree_entry_free(e); git_vector_free(&tree->entries); + git_pool_clear(&tree->pool); git__free(tree); } @@ -297,6 +336,7 @@ const git_tree_entry *git_tree_entry_byname( const git_tree *tree, const char *filename) { assert(tree && filename); + return entry_fromname(tree, filename, strlen(filename)); } @@ -327,13 +367,16 @@ int git_tree__prefix_position(const git_tree *tree, const char *path) { const git_vector *entries = &tree->entries; struct tree_key_search ksearch; - size_t at_pos; + size_t at_pos, path_len; if (!path) return 0; + path_len = strlen(path); + TREE_ENTRY_CHECK_NAMELEN(path_len); + ksearch.filename = path; - ksearch.filename_len = strlen(path); + ksearch.filename_len = (uint16_t)path_len; /* be safe when we cast away constness - i.e. don't trigger a sort */ assert(git_vector_is_sorted(&tree->entries)); @@ -379,51 +422,68 @@ static int tree_error(const char *str, const char *path) return -1; } +static int parse_mode(unsigned int *modep, const char *buffer, const char **buffer_out) +{ + unsigned char c; + unsigned int mode = 0; + + if (*buffer == ' ') + return -1; + + while ((c = *buffer++) != ' ') { + if (c < '0' || c > '7') + return -1; + mode = (mode << 3) + (c - '0'); + } + *modep = mode; + *buffer_out = buffer; + + return 0; +} + int git_tree__parse(void *_tree, git_odb_object *odb_obj) { git_tree *tree = _tree; const char *buffer = git_odb_object_data(odb_obj); const char *buffer_end = buffer + git_odb_object_size(odb_obj); + git_pool_init(&tree->pool, 1); if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0) return -1; while (buffer < buffer_end) { git_tree_entry *entry; - int attr; + size_t filename_len; + const char *nul; + unsigned int attr; - if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer) + if (parse_mode(&attr, buffer, &buffer) < 0 || !buffer) return tree_error("Failed to parse tree. Can't parse filemode", NULL); - if (*buffer++ != ' ') - return tree_error("Failed to parse tree. Object is corrupted", NULL); - - if (memchr(buffer, 0, buffer_end - buffer) == NULL) + if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) return tree_error("Failed to parse tree. Object is corrupted", NULL); + filename_len = nul - buffer; /** Allocate the entry and store it in the entries vector */ { - entry = alloc_entry(buffer); + entry = alloc_entry_pooled(&tree->pool, buffer, filename_len); GITERR_CHECK_ALLOC(entry); - if (git_vector_insert(&tree->entries, entry) < 0) { - git__free(entry); + if (git_vector_insert(&tree->entries, entry) < 0) return -1; - } entry->attr = attr; } - while (buffer < buffer_end && *buffer != 0) - buffer++; - - buffer++; + /* Advance to the ID just after the path */ + buffer += filename_len + 1; git_oid_fromraw(&entry->oid, (const unsigned char *)buffer); buffer += GIT_OID_RAWSZ; } - git_vector_sort(&tree->entries); + /* The tree is sorted by definition. Bad inputs give bad outputs */ + tree->entries.flags |= GIT_VECTOR_SORTED; return 0; } @@ -666,6 +726,18 @@ on_error: return -1; } +static git_otype otype_from_mode(git_filemode_t filemode) +{ + switch (filemode) { + case GIT_FILEMODE_TREE: + return GIT_OBJ_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJ_COMMIT; + default: + return GIT_OBJ_BLOB; + } +} + int git_treebuilder_insert( const git_tree_entry **entry_out, git_treebuilder *bld, @@ -685,6 +757,10 @@ int git_treebuilder_insert( if (!valid_entry_name(bld->repo, filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", 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); + pos = git_strmap_lookup_index(bld->map, filename); if (git_strmap_valid_index(bld->map, pos)) { entry = git_strmap_value_at(bld->map, pos); diff --git a/src/tree.h b/src/tree.h index d01b6fd41..914d788c8 100644 --- a/src/tree.h +++ b/src/tree.h @@ -12,17 +12,20 @@ #include "odb.h" #include "vector.h" #include "strmap.h" +#include "pool.h" struct git_tree_entry { uint16_t attr; + uint16_t filename_len; git_oid oid; - size_t filename_len; - char filename[1]; + bool pooled; + char filename[GIT_FLEX_ARRAY]; }; struct git_tree { git_object object; git_vector entries; + git_pool pool; }; struct git_treebuilder { diff --git a/src/unix/map.c b/src/unix/map.c index 87ee6594b..72abb3418 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -17,7 +17,7 @@ int git__page_size(size_t *page_size) { long sc_page_size = sysconf(_SC_PAGE_SIZE); if (sc_page_size < 0) { - giterr_set_str(GITERR_OS, "Can't determine system page size"); + giterr_set(GITERR_OS, "can't determine system page size"); return -1; } *page_size = (size_t) sc_page_size; diff --git a/src/unix/posix.h b/src/unix/posix.h index 777350990..482d2c803 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -21,8 +21,19 @@ typedef int GIT_SOCKET; #define p_lstat(p,b) lstat(p,b) #define p_stat(p,b) stat(p, b) +#if defined(GIT_USE_STAT_MTIMESPEC) +# define st_atime_nsec st_atimespec.tv_nsec +# define st_mtime_nsec st_mtimespec.tv_nsec +# define st_ctime_nsec st_ctimespec.tv_nsec +#elif defined(GIT_USE_STAT_MTIM) +# define st_atime_nsec st_atim.tv_nsec +# define st_mtime_nsec st_mtim.tv_nsec +# define st_ctime_nsec st_ctim.tv_nsec +#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NEC) +# error GIT_USE_NSEC defined but unknown struct stat nanosecond type +#endif + #define p_utimes(f, t) utimes(f, t) -#define p_futimes(f, t) futimes(f, t) #define p_readlink(a, b, c) readlink(a, b, c) #define p_symlink(o,n) symlink(o, n) @@ -53,4 +64,20 @@ extern char *p_realpath(const char *, char *); #define p_localtime_r(c, r) localtime_r(c, r) #define p_gmtime_r(c, r) gmtime_r(c, r) +#define p_timeval timeval + +#ifdef HAVE_FUTIMENS +GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) +{ + struct timespec s[2]; + s[0].tv_sec = t[0].tv_sec; + s[0].tv_nsec = t[0].tv_usec * 1000; + s[1].tv_sec = t[1].tv_sec; + s[1].tv_nsec = t[1].tv_usec * 1000; + return futimens(f, s); +} +#else +# define p_futimes futimes +#endif + #endif diff --git a/src/util.c b/src/util.c index c62826420..9e67f4347 100644 --- a/src/util.c +++ b/src/util.c @@ -10,6 +10,10 @@ #include #include "posix.h" +#ifdef GIT_WIN32 +# include "win32/w32_buffer.h" +#endif + #ifdef _MSC_VER # include #endif @@ -607,7 +611,7 @@ size_t git__unescape(char *str) return (pos - str); } -#if defined(GIT_WIN32) || defined(BSD) +#if defined(HAVE_QSORT_S) || (defined(HAVE_QSORT_R) && defined(BSD)) typedef struct { git__sort_r_cmp cmp; void *payload; @@ -624,21 +628,16 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp( void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) { -#if defined(__MINGW32__) || defined(AMIGA) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || \ - defined(__gnu_hurd__) || defined(__ANDROID_API__) || \ - defined(__sun) || defined(__CYGWIN__) || \ - (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) || \ - (defined(_MSC_VER) && _MSC_VER < 1500) - git__insertsort_r(els, nel, elsize, NULL, cmp, payload); -#elif defined(GIT_WIN32) - git__qsort_r_glue glue = { cmp, payload }; - qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); -#elif defined(BSD) +#if defined(HAVE_QSORT_R) && defined(BSD) git__qsort_r_glue glue = { cmp, payload }; qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#else +#elif defined(HAVE_QSORT_R) && defined(__GLIBC__) qsort_r(els, nel, elsize, cmp, payload); +#elif defined(HAVE_QSORT_S) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#else + git__insertsort_r(els, nel, elsize, NULL, cmp, payload); #endif } @@ -765,3 +764,47 @@ int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst) *dst = uc; return length; } + +#ifdef GIT_WIN32 +int git__getenv(git_buf *out, const char *name) +{ + wchar_t *wide_name = NULL, *wide_value = NULL; + DWORD value_len; + int error = -1; + + git_buf_clear(out); + + if (git__utf8_to_16_alloc(&wide_name, name) < 0) + return -1; + + if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { + wide_value = git__malloc(value_len * sizeof(wchar_t)); + GITERR_CHECK_ALLOC(wide_value); + + value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); + } + + if (value_len) + error = git_buf_put_w(out, wide_value, value_len); + else if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) + error = GIT_ENOTFOUND; + else + giterr_set(GITERR_OS, "could not read environment variable '%s'", name); + + git__free(wide_name); + git__free(wide_value); + return error; +} +#else +int git__getenv(git_buf *out, const char *name) +{ + const char *val = getenv(name); + + git_buf_clear(out); + + if (!val) + return GIT_ENOTFOUND; + + return git_buf_puts(out, val); +} +#endif diff --git a/src/util.h b/src/util.h index b2abbe6a6..d0c3cd04a 100644 --- a/src/util.h +++ b/src/util.h @@ -7,6 +7,9 @@ #ifndef INCLUDE_util_h__ #define INCLUDE_util_h__ +#include "git2/buffer.h" +#include "buffer.h" + #if defined(GIT_MSVC_CRTDBG) /* Enable MSVC CRTDBG memory leak reporting. * @@ -35,6 +38,7 @@ */ #include #include +#include "win32/w32_crtdbg_stacktrace.h" #endif #include "common.h" @@ -62,23 +66,24 @@ #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, file, 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, file, 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, file, line); + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); if (!ptr) giterr_set_oom(); return ptr; } @@ -118,7 +123,7 @@ GIT_INLINE(char *) git__crtdbg__substrdup(const char *start, size_t n, const cha 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, file, 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; } @@ -126,8 +131,9 @@ GIT_INLINE(void *) git__crtdbg__realloc(void *ptr, size_t size, const char *file 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, file, line); + 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) @@ -596,4 +602,6 @@ GIT_INLINE(double) git__timer(void) #endif +extern int git__getenv(git_buf *out, const char *name); + #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 93d09bb5b..a81d463ef 100644 --- a/src/vector.c +++ b/src/vector.c @@ -40,6 +40,13 @@ GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) return 0; } +int git_vector_size_hint(git_vector *v, size_t size_hint) +{ + if (v->_alloc_size >= size_hint) + return 0; + return resize_vector(v, size_hint); +} + int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) { size_t bytes; diff --git a/src/vector.h b/src/vector.h index aac46c4b3..b7500ded3 100644 --- a/src/vector.h +++ b/src/vector.h @@ -32,6 +32,7 @@ void git_vector_free_deep(git_vector *v); /* free each entry and self */ void git_vector_clear(git_vector *v); int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp); void git_vector_swap(git_vector *a, git_vector *b); +int git_vector_size_hint(git_vector *v, size_t size_hint); void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); diff --git a/src/win32/findfile.c b/src/win32/findfile.c index de27dd060..58c22279e 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -215,3 +215,13 @@ int git_win32__find_xdg_dirs(git_buf *out) return win32_find_existing_dirs(out, global_tmpls); } + +int git_win32__find_programdata_dirs(git_buf *out) +{ + static const wchar_t *programdata_tmpls[2] = { + L"%PROGRAMDATA%\\Git", + NULL, + }; + + return win32_find_existing_dirs(out, programdata_tmpls); +} diff --git a/src/win32/findfile.h b/src/win32/findfile.h index a50319b9a..3d5fff439 100644 --- a/src/win32/findfile.h +++ b/src/win32/findfile.h @@ -11,6 +11,7 @@ extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath); extern int git_win32__find_global_dirs(git_buf *out); extern int git_win32__find_xdg_dirs(git_buf *out); +extern int git_win32__find_programdata_dirs(git_buf *out); #endif diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index a4a5a31c7..698ebed1a 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -11,12 +11,6 @@ #undef stat -#if _WIN32_WINNT >= 0x0601 -#define stat __stat64 -#else -#define stat _stati64 -#endif - #if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) #undef MemoryBarrier void __mingworg_MemoryBarrier(void); diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h index 8004bc1f8..12b50d981 100644 --- a/src/win32/msvc-compat.h +++ b/src/win32/msvc-compat.h @@ -9,9 +9,6 @@ #if defined(_MSC_VER) -/* 64-bit stat information, regardless of USE_32BIT_TIME_T define */ -#define stat __stat64 - typedef unsigned short mode_t; typedef SSIZE_T ssize_t; diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index 118e8bcc5..40b95c33b 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -198,13 +198,13 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) /* See if this is an absolute path (beginning with a drive letter) */ if (path__is_absolute(src)) { if (git__utf8_to_16(dest, MAX_PATH, src) < 0) - return -1; + goto on_error; } /* File-prefixed NT-style paths beginning with \\?\ */ else if (path__is_nt_namespace(src)) { /* Skip the NT prefix, the destination already contains it */ if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0) - return -1; + goto on_error; } /* UNC paths */ else if (path__is_unc(src)) { @@ -213,36 +213,43 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) /* Skip the leading "\\" */ if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0) - return -1; + goto on_error; } /* Absolute paths omitting the drive letter */ else if (src[0] == '\\' || src[0] == '/') { if (path__cwd(dest, MAX_PATH) < 0) - return -1; + goto on_error; if (!path__is_absolute(dest)) { errno = ENOENT; - return -1; + goto on_error; } /* Skip the drive letter specification ("C:") */ if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0) - return -1; + goto on_error; } /* Relative paths */ else { int cwd_len; if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0) - return -1; + goto on_error; dest[cwd_len++] = L'\\'; if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0) - return -1; + goto on_error; } return git_win32_path_canonicalize(out); + +on_error: + /* set windows error code so we can use its error message */ + if (errno == ENAMETOOLONG) + SetLastError(ERROR_FILENAME_EXCED_RANGE); + + return -1; } int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) diff --git a/src/win32/posix.h b/src/win32/posix.h index ac98fd864..5fab267c2 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -9,6 +9,7 @@ #include "common.h" #include "../posix.h" +#include "win32-compat.h" #include "path_w32.h" #include "utf-conv.h" #include "dir.h" @@ -16,12 +17,13 @@ typedef SOCKET GIT_SOCKET; #define p_lseek(f,n,w) _lseeki64(f, n, w) -#define p_fstat(f,b) _fstat64(f, b) -extern int p_lstat(const char *file_name, struct stat *buf); -extern int p_stat(const char* path, struct stat* buf); -extern int p_utimes(const char *filename, const struct timeval times[2]); -extern int p_futimes(int fd, const struct timeval times[2]); +extern int p_fstat(int fd, struct stat *buf); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_stat(const char* path, struct stat *buf); + +extern int p_utimes(const char *filename, const struct p_timeval times[2]); +extern int p_futimes(int fd, const struct p_timeval times[2]); extern int p_readlink(const char *path, char *buf, size_t bufsiz); extern int p_symlink(const char *old, const char *new); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 504562b0e..fea634b00 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -11,6 +11,8 @@ #include "utf-conv.h" #include "repository.h" #include "reparse.h" +#include "global.h" +#include "buffer.h" #include #include #include @@ -146,12 +148,19 @@ static int lstat_w( return git_win32__file_attribute_to_stat(buf, &fdata, path); } - errno = ENOENT; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } /* To match POSIX behavior, set ENOTDIR when any of the folders in the * file path is a regular file, otherwise set ENOENT. */ - if (posix_enotdir) { + if (errno == ENOENT && posix_enotdir) { size_t path_len = wcslen(path); /* scan up path until we find an existing item */ @@ -201,7 +210,7 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, true); } -int p_utimes(const char *filename, const struct timeval times[2]) +int p_utimes(const char *filename, const struct p_timeval times[2]) { int fd, error; @@ -214,7 +223,7 @@ int p_utimes(const char *filename, const struct timeval times[2]) return error; } -int p_futimes(int fd, const struct timeval times[2]) +int p_futimes(int fd, const struct p_timeval times[2]) { HANDLE handle; FILETIME atime = {0}, mtime = {0}; @@ -389,6 +398,22 @@ static int follow_and_lstat_link(git_win32_path path, struct stat* buf) return lstat_w(target_w, buf, false); } +int p_fstat(int fd, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fhInfo; + + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE || + !GetFileInformationByHandle(fh, &fhInfo)) { + errno = EBADF; + return -1; + } + + git_win32__file_information_to_stat(buf, &fhInfo); + return 0; +} + int p_stat(const char* path, struct stat* buf) { git_win32_path path_w; diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index f1b674ea0..96fd4606e 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -8,20 +8,6 @@ #include "common.h" #include "utf-conv.h" -GIT_INLINE(DWORD) get_wc_flags(void) -{ - static char inited = 0; - static DWORD flags; - - /* Invalid code point check supported on Vista+ only */ - if (!inited) { - flags = git_has_win32_version(6, 0, 0) ? WC_ERR_INVALID_CHARS : 0; - inited = 1; - } - - return flags; -} - GIT_INLINE(void) git__set_errno(void) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) @@ -66,7 +52,7 @@ int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - if ((len = WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) git__set_errno(); return len; @@ -127,12 +113,11 @@ int git__utf8_to_16_alloc(wchar_t **dest, const char *src) int git__utf16_to_8_alloc(char **dest, const wchar_t *src) { int utf8_size; - DWORD dwFlags = get_wc_flags(); *dest = NULL; /* Length of -1 indicates NULL termination of the input string */ - utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, NULL, 0, NULL, NULL); + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); if (!utf8_size) { git__set_errno(); @@ -146,7 +131,7 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src) return -1; } - utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL); + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); if (!utf8_size) { git__set_errno(); diff --git a/src/win32/buffer.c b/src/win32/w32_buffer.c similarity index 98% rename from src/win32/buffer.c rename to src/win32/w32_buffer.c index 74950189e..9122baaa6 100644 --- a/src/win32/buffer.c +++ b/src/win32/w32_buffer.c @@ -6,7 +6,7 @@ */ #include "common.h" -#include "buffer.h" +#include "w32_buffer.h" #include "../buffer.h" #include "utf-conv.h" @@ -52,4 +52,3 @@ int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w) buf->ptr[buf->size] = '\0'; return 0; } - diff --git a/src/win32/buffer.h b/src/win32/w32_buffer.h similarity index 100% rename from src/win32/buffer.h rename to src/win32/w32_buffer.h diff --git a/src/win32/w32_crtdbg_stacktrace.c b/src/win32/w32_crtdbg_stacktrace.c new file mode 100644 index 000000000..a778f4164 --- /dev/null +++ b/src/win32/w32_crtdbg_stacktrace.c @@ -0,0 +1,343 @@ +/* + * 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. + */ + +#if defined(GIT_MSVC_CRTDBG) +#include "w32_stack.h" +#include "w32_crtdbg_stacktrace.h" + +#define CRTDBG_STACKTRACE__UID_LEN (15) + +/** + * The stacktrace of an allocation can be distilled + * to a unique id based upon the stackframe pointers + * and ignoring any size arguments. We will use these + * UIDs as the (char const*) __FILE__ argument we + * give to the CRT malloc routines. + */ +typedef struct { + char uid[CRTDBG_STACKTRACE__UID_LEN + 1]; +} git_win32__crtdbg_stacktrace__uid; + +/** + * All mallocs with the same stacktrace will be de-duped + * and aggregated into this row. + */ +typedef struct { + git_win32__crtdbg_stacktrace__uid uid; /* must be first */ + git_win32__stack__raw_data raw_data; + unsigned int count_allocs; /* times this alloc signature seen since init */ + unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ + unsigned int transient_count_leaks; /* sum of leaks */ +} git_win32__crtdbg_stacktrace__row; + +static CRITICAL_SECTION g_crtdbg_stacktrace_cs; + +/** + * CRTDBG memory leak tracking takes a "char const * const file_name" + * and stores the pointer in the heap data (instead of allocing a copy + * for itself). Normally, this is not a problem, since we usually pass + * in __FILE__. But I'm going to lie to it and pass in the address of + * the UID in place of the file_name. Also, I do not want to alloc the + * stacktrace data (because we are called from inside our alloc routines). + * Therefore, I'm creating a very large static pool array to store row + * data. This also eliminates the temptation to realloc it (and move the + * UID pointers). + * + * And to efficiently look for duplicates we need an index on the rows + * so we can bsearch it. Again, without mallocing. + * + * If we observe more than MY_ROW_LIMIT unique malloc signatures, we + * fall through and use the traditional __FILE__ processing and don't + * try to de-dup them. If your testing hits this limit, just increase + * it and try again. + */ + +#define MY_ROW_LIMIT (1024 * 1024) +static git_win32__crtdbg_stacktrace__row g_cs_rows[MY_ROW_LIMIT]; +static git_win32__crtdbg_stacktrace__row *g_cs_index[MY_ROW_LIMIT]; + +static unsigned int g_cs_end = MY_ROW_LIMIT; +static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ +static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ +static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ +static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ +static bool g_limit_reached = false; /* had allocs after we filled row table */ + +static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ +static bool g_transient_leaks_since_mark = false; /* payload for hook */ + +/** + * Compare function for bsearch on g_cs_index table. + */ +static int row_cmp(const void *v1, const void *v2) +{ + git_win32__stack__raw_data *d1 = (git_win32__stack__raw_data*)v1; + git_win32__crtdbg_stacktrace__row *r2 = (git_win32__crtdbg_stacktrace__row *)v2; + + return (git_win32__stack_compare(d1, &r2->raw_data)); +} + +/** + * Unique insert the new data into the row and index tables. + * We have to sort by the stackframe data itself, not the uid. + */ +static git_win32__crtdbg_stacktrace__row * insert_unique( + const git_win32__stack__raw_data *pdata) +{ + size_t pos; + if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { + /* Append new unique item to row table. */ + memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); + sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); + + /* Insert pointer to it into the proper place in the index table. */ + if (pos < g_cs_ins) + memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); + g_cs_index[pos] = &g_cs_rows[g_cs_ins]; + + g_cs_ins++; + } + + g_cs_index[pos]->count_allocs++; + + return g_cs_index[pos]; +} + +/** + * Hook function to receive leak data from the CRT. (This includes + * both ":()" data, but also each of the + * various headers and fields. + * + * Scan this for the special "##" UID forms that we substituted + * for the "". Map back to the row data and + * increment its leak count. + * + * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx + * + * We suppress the actual crtdbg output. + */ +static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) +{ + static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ + unsigned int pos; + + *retVal = 0; /* do not invoke debugger */ + + if ((szMsg[0] != '#') || (szMsg[1] != '#')) + return hook_result; + + if (sscanf(&szMsg[2], "%08lx", &pos) < 1) + return hook_result; + if (pos >= g_cs_ins) + return hook_result; + + if (g_transient_leaks_since_mark) { + if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) + return hook_result; + } + + g_cs_rows[pos].transient_count_leaks++; + + if (g_cs_rows[pos].transient_count_leaks == 1) + g_transient_count_dedup_leaks++; + + g_transient_count_total_leaks++; + + return hook_result; +} + +/** + * Write leak data to all of the various places we need. + * We force the caller to sprintf() the message first + * because we want to avoid fprintf() because it allocs. + */ +static void my_output(const char *buf) +{ + fwrite(buf, strlen(buf), 1, stderr); + OutputDebugString(buf); +} + +/** + * For each row with leaks, dump a stacktrace for it. + */ +static void dump_summary(const char *label) +{ + unsigned int k; + char buf[10 * 1024]; + + if (g_transient_count_total_leaks == 0) + return; + + fflush(stdout); + fflush(stderr); + my_output("\n"); + + if (g_limit_reached) { + sprintf(buf, + "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", + MY_ROW_LIMIT); + my_output(buf); + } + + if (!label) + label = ""; + + if (g_transient_leaks_since_mark) { + sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", + g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } else { + sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", + g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } + my_output("\n"); + + for (k = 0; k < g_cs_ins; k++) { + if (g_cs_rows[k].transient_count_leaks > 0) { + sprintf(buf, "LEAK: %s leaked %d of %d times:\n", + g_cs_rows[k].uid.uid, + g_cs_rows[k].transient_count_leaks, + g_cs_rows[k].count_allocs); + my_output(buf); + + if (git_win32__stack_format( + buf, sizeof(buf), &g_cs_rows[k].raw_data, + NULL, NULL) >= 0) { + my_output(buf); + } + + my_output("\n"); + } + } + + fflush(stderr); +} + +void git_win32__crtdbg_stacktrace_init(void) +{ + InitializeCriticalSection(&g_crtdbg_stacktrace_cs); + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); +} + +int git_win32__crtdbg_stacktrace__dump( + git_win32__crtdbg_stacktrace_options opt, + const char *label) +{ + _CRT_REPORT_HOOK old; + unsigned int k; + int r = 0; + +#define IS_BIT_SET(o,b) (((o) & (b)) != 0) + + bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK); + bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK); + bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL); + bool b_quiet = IS_BIT_SET(opt, GIT_WIN32__CRTDBG_STACKTRACE__QUIET); + + if (b_leaks_since_mark && b_leaks_total) { + giterr_set(GITERR_INVALID, "Cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); + return GIT_ERROR; + } + if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { + giterr_set(GITERR_INVALID, "Nothing to do."); + return GIT_ERROR; + } + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (b_leaks_since_mark || b_leaks_total) { + /* All variables with "transient" in the name are per-dump counters + * and reset before each dump. This lets us handle checkpoints. + */ + g_transient_count_total_leaks = 0; + g_transient_count_dedup_leaks = 0; + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].transient_count_leaks = 0; + } + } + + g_transient_leaks_since_mark = b_leaks_since_mark; + + old = _CrtSetReportHook(report_hook); + _CrtDumpMemoryLeaks(); + _CrtSetReportHook(old); + + if (b_leaks_since_mark || b_leaks_total) { + r = g_transient_count_dedup_leaks; + + if (!b_quiet) + dump_summary(label); + } + + if (b_set_mark) { + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; + } + + g_checkpoint_id++; + } + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return r; +} + +void git_win32__crtdbg_stacktrace_cleanup(void) +{ + /* At shutdown/cleanup, dump cummulative leak info + * with everything since startup. This might generate + * extra noise if the caller has been doing checkpoint + * dumps, but it might also eliminate some false + * positives for resources previously reported during + * checkpoints. + */ + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL, + "CLEANUP"); + + DeleteCriticalSection(&g_crtdbg_stacktrace_cs); +} + +const char *git_win32__crtdbg_stacktrace(int skip, const char *file) +{ + git_win32__stack__raw_data new_data; + git_win32__crtdbg_stacktrace__row *row; + const char * result = file; + + if (git_win32__stack_capture(&new_data, skip+1) < 0) + return result; + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (g_cs_ins < g_cs_end) { + row = insert_unique(&new_data); + result = row->uid.uid; + } else { + g_limit_reached = true; + } + + g_count_total_allocs++; + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return result; +} +#endif diff --git a/src/win32/w32_crtdbg_stacktrace.h b/src/win32/w32_crtdbg_stacktrace.h new file mode 100644 index 000000000..40ca60d53 --- /dev/null +++ b/src/win32/w32_crtdbg_stacktrace.h @@ -0,0 +1,93 @@ +/* + * 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_w32_crtdbg_stacktrace_h__ +#define INCLUDE_w32_crtdbg_stacktrace_h__ + +#if defined(GIT_MSVC_CRTDBG) + +/** + * Initialize our memory leak tracking and de-dup data structures. + * This should ONLY be called by git_libgit2_init(). + */ +void git_win32__crtdbg_stacktrace_init(void); + +/** + * Shutdown our memory leak tracking and dump summary data. + * This should ONLY be called by git_libgit2_shutdown(). + * + * We explicitly call _CrtDumpMemoryLeaks() during here so + * that we can compute summary data for the leaks. We print + * the stacktrace of each unique leak. + * + * This cleanup does not happen if the app calls exit() + * without calling the libgit2 shutdown code. + * + * This info we print here is independent of any automatic + * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. + * Set it in your app if you also want traditional reporting. + */ +void git_win32__crtdbg_stacktrace_cleanup(void); + +/** + * Checkpoint options. + */ +typedef enum git_win32__crtdbg_stacktrace_options { + /** + * Set checkpoint marker. + */ + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK = (1 << 0), + + /** + * Dump leaks since last checkpoint marker. + * May not be combined with __LEAKS_TOTAL. + * + * Note that this may generate false positives for global TLS + * error state and other global caches that aren't cleaned up + * until the thread/process terminates. So when using this + * around a region of interest, also check the final (at exit) + * dump before digging into leaks reported here. + */ + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK = (1 << 1), + + /** + * Dump leaks since init. May not be combined + * with __LEAKS_SINCE_MARK. + */ + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL = (1 << 2), + + /** + * Suppress printing during dumps. + * Just return leak count. + */ + GIT_WIN32__CRTDBG_STACKTRACE__QUIET = (1 << 3), + +} git_win32__crtdbg_stacktrace_options; + +/** + * Checkpoint memory state and/or dump unique stack traces of + * current memory leaks. + * + * @return number of unique leaks (relative to requested starting + * point) or error. + */ +GIT_EXTERN(int) git_win32__crtdbg_stacktrace__dump( + git_win32__crtdbg_stacktrace_options opt, + const char *label); + +/** + * Construct stacktrace and append it to the global buffer. + * Return pointer to start of this string. On any error or + * lack of buffer space, just return the given file buffer + * so it will behave as usual. + * + * This should ONLY be called by our internal memory allocations + * routines. + */ +const char *git_win32__crtdbg_stacktrace(int skip, const char *file); + +#endif +#endif diff --git a/src/win32/w32_stack.c b/src/win32/w32_stack.c new file mode 100644 index 000000000..15af3dcb7 --- /dev/null +++ b/src/win32/w32_stack.c @@ -0,0 +1,192 @@ +/* + * 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. + */ + +#if defined(GIT_MSVC_CRTDBG) +#include "Windows.h" +#include "Dbghelp.h" +#include "win32/posix.h" +#include "w32_stack.h" +#include "hash.h" + +/** + * This is supposedly defined in WinBase.h (from Windows.h) but there were linker issues. + */ +USHORT WINAPI RtlCaptureStackBackTrace(ULONG, ULONG, PVOID*, PULONG); + +static bool g_win32_stack_initialized = false; +static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; +static git_win32__stack__aux_cb_alloc g_aux_cb_alloc = NULL; +static git_win32__stack__aux_cb_lookup g_aux_cb_lookup = NULL; + +int git_win32__stack__set_aux_cb( + git_win32__stack__aux_cb_alloc cb_alloc, + git_win32__stack__aux_cb_lookup cb_lookup) +{ + g_aux_cb_alloc = cb_alloc; + g_aux_cb_lookup = cb_lookup; + + return 0; +} + +void git_win32__stack_init(void) +{ + if (!g_win32_stack_initialized) { + g_win32_stack_process = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES); + SymInitialize(g_win32_stack_process, NULL, TRUE); + g_win32_stack_initialized = true; + } +} + +void git_win32__stack_cleanup(void) +{ + if (g_win32_stack_initialized) { + SymCleanup(g_win32_stack_process); + g_win32_stack_process = INVALID_HANDLE_VALUE; + g_win32_stack_initialized = false; + } +} + +int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip) +{ + if (!g_win32_stack_initialized) { + giterr_set(GITERR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + memset(pdata, 0, sizeof(*pdata)); + pdata->nr_frames = RtlCaptureStackBackTrace( + skip+1, GIT_WIN32__STACK__MAX_FRAMES, pdata->frames, NULL); + + /* If an "aux" data provider was registered, ask it to capture + * whatever data it needs and give us an "aux_id" to it so that + * we can refer to it later when reporting. + */ + if (g_aux_cb_alloc) + (g_aux_cb_alloc)(&pdata->aux_id); + + return 0; +} + +int git_win32__stack_compare( + git_win32__stack__raw_data *d1, + git_win32__stack__raw_data *d2) +{ + return memcmp(d1, d2, sizeof(*d1)); +} + +int git_win32__stack_format( + char *pbuf, int buf_len, + const git_win32__stack__raw_data *pdata, + const char *prefix, const char *suffix) +{ +#define MY_MAX_FILENAME 255 + + /* SYMBOL_INFO has char FileName[1] at the end. The docs say to + * to malloc it with extra space for your desired max filename. + */ + struct { + SYMBOL_INFO symbol; + char extra[MY_MAX_FILENAME + 1]; + } s; + + IMAGEHLP_LINE64 line; + int buf_used = 0; + unsigned int k; + char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ + int detail_len; + + if (!g_win32_stack_initialized) { + giterr_set(GITERR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + if (!prefix) + prefix = "\t"; + if (!suffix) + suffix = "\n"; + + memset(pbuf, 0, buf_len); + + memset(&s, 0, sizeof(s)); + s.symbol.MaxNameLen = MY_MAX_FILENAME; + s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + + memset(&line, 0, sizeof(line)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (k=0; k < pdata->nr_frames; k++) { + DWORD64 frame_k = (DWORD64)pdata->frames[k]; + DWORD dwUnused; + + if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && + SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { + const char *pslash; + const char *pfile; + + pslash = strrchr(line.FileName, '\\'); + pfile = ((pslash) ? (pslash+1) : line.FileName); + p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", + prefix, pfile, line.LineNumber, s.symbol.Name, suffix); + } else { + /* This happens when we cross into another module. + * For example, in CLAR tests, this is typically + * the CRT startup code. Just print an unknown + * frame and continue. + */ + p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); + } + detail_len = strlen(detail); + + if (buf_len < (buf_used + detail_len + 1)) { + /* we don't have room for this frame in the buffer, so just stop. */ + break; + } + + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle + * allocs that occur before the aux callbacks were registered. + */ + if (pdata->aux_id > 0) { + p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", + prefix, pdata->aux_id, suffix); + detail_len = strlen(detail); + if ((buf_used + detail_len + 1) < buf_len) { + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* If an "aux" data provider is still registered, ask it to append its detailed + * data to the end of ours using the "aux_id" it gave us when this de-duped + * item was created. + */ + if (g_aux_cb_lookup) + (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); + } + + return GIT_OK; +} + +int git_win32__stack( + char * pbuf, int buf_len, + int skip, + const char *prefix, const char *suffix) +{ + git_win32__stack__raw_data data; + int error; + + if ((error = git_win32__stack_capture(&data, skip)) < 0) + return error; + if ((error = git_win32__stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) + return error; + return 0; +} + +#endif diff --git a/src/win32/w32_stack.h b/src/win32/w32_stack.h new file mode 100644 index 000000000..21170bd2f --- /dev/null +++ b/src/win32/w32_stack.h @@ -0,0 +1,138 @@ +/* + * 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_w32_stack_h__ +#define INCLUDE_w32_stack_h__ + +#if defined(GIT_MSVC_CRTDBG) + +/** + * This type defines a callback to be used to augment a C stacktrace + * with "aux" data. This can be used, for example, to allow LibGit2Sharp + * (or other interpreted consumer libraries) to give us C# stacktrace + * data for the PInvoke. + * + * This callback will be called during crtdbg-instrumented allocs. + * + * @param aux_id [out] A returned "aux_id" representing a unique + * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved + * to mean no aux stacktrace data. + */ +typedef void (*git_win32__stack__aux_cb_alloc)(unsigned int *aux_id); + +/** + * This type defines a callback to be used to augment the output of + * a stacktrace. This will be used to request the C# layer format + * the C# stacktrace associated with "aux_id" into the provided + * buffer. + * + * This callback will be called during leak reporting. + * + * @param aux_id The "aux_id" key associated with a stacktrace. + * @param aux_msg A buffer where a formatted message should be written. + * @param aux_msg_len The size of the buffer. + */ +typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len); + +/** + * Register an "aux" data provider to augment our C stacktrace data. + * + * This can be used, for example, to allow LibGit2Sharp (or other + * interpreted consumer libraries) to give us the C# stacktrace of + * the PInvoke. + * + * If you choose to use this feature, it should be registered during + * initialization and not changed for the duration of the process. + */ +GIT_EXTERN(int) git_win32__stack__set_aux_cb( + git_win32__stack__aux_cb_alloc cb_alloc, + git_win32__stack__aux_cb_lookup cb_lookup); + +/** + * Maximum number of stackframes to record for a + * single stacktrace. + */ +#define GIT_WIN32__STACK__MAX_FRAMES 30 + +/** + * Wrapper containing the raw unprocessed stackframe + * data for a single stacktrace and any "aux_id". + * + * I put the aux_id first so leaks will be sorted by it. + * So, for example, if a specific callstack in C# leaks + * a repo handle, all of the pointers within the associated + * repo pointer will be grouped together. + */ +typedef struct { + unsigned int aux_id; + unsigned int nr_frames; + void *frames[GIT_WIN32__STACK__MAX_FRAMES]; +} git_win32__stack__raw_data; + + +/** + * Load symbol table data. This should be done in the primary + * thread at startup (under a lock if there are other threads + * active). + */ +void git_win32__stack_init(void); + +/** + * Cleanup symbol table data. This should be done in the + * primary thead at shutdown (under a lock if there are other + * threads active). + */ +void git_win32__stack_cleanup(void); + + +/** + * Capture raw stack trace data for the current process/thread. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + */ +int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip); + +/** + * Compare 2 raw stacktraces with the usual -1,0,+1 result. + * This includes any "aux_id" values in the comparison, so that + * our de-dup is also "aux" context relative. + */ +int git_win32__stack_compare( + git_win32__stack__raw_data *d1, + git_win32__stack__raw_data *d2); + +/** + * Format raw stacktrace data into buffer WITHOUT using any mallocs. + * + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32__stack_format( + char *pbuf, int buf_len, + const git_win32__stack__raw_data *pdata, + const char *prefix, const char *suffix); + +/** + * Convenience routine to capture and format stacktrace into + * a buffer WITHOUT using any mallocs. This is primarily a + * wrapper for testing. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32__stack( + char * pbuf, int buf_len, + int skip, + const char *prefix, const char *suffix); + +#endif /* GIT_MSVC_CRTDBG */ +#endif /* INCLUDE_w32_stack_h__ */ diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c index 2e52525d5..60311bb50 100644 --- a/src/win32/w32_util.c +++ b/src/win32/w32_util.c @@ -48,7 +48,35 @@ bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) * @param path The path which should receive the +H bit. * @return 0 on success; -1 on failure */ -int git_win32__sethidden(const char *path) +int git_win32__set_hidden(const char *path, bool hidden) +{ + git_win32_path buf; + DWORD attrs, newattrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + if (hidden) + newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; + else + newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; + + if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { + giterr_set(GITERR_OS, "Failed to %s hidden bit for '%s'", + hidden ? "set" : "unset", path); + return -1; + } + + return 0; +} + +int git_win32__hidden(bool *out, const char *path) { git_win32_path buf; DWORD attrs; @@ -62,11 +90,7 @@ int git_win32__sethidden(const char *path) if (attrs == INVALID_FILE_ATTRIBUTES) return -1; - /* If the item isn't already +H, add the bit */ - if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 && - !SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN)) - return -1; - + *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; return 0; } diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 377d651a8..2e475e5e9 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -40,12 +40,22 @@ GIT_INLINE(bool) git_win32__isalpha(wchar_t c) bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); /** - * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * Ensures the given path (file or folder) has the +H (hidden) attribute set + * or unset. * - * @param path The path which should receive the +H bit. + * @param path The path that should receive the +H bit. + * @param hidden true to set +H, false to unset it * @return 0 on success; -1 on failure */ -int git_win32__sethidden(const char *path); +extern int git_win32__set_hidden(const char *path, bool hidden); + +/** + * Determines if the given file or folder has the hidden attribute set. + * @param hidden pointer to store hidden value + * @param path The path that should be queried for hiddenness. + * @return 0 on success or an error code. + */ +extern int git_win32__hidden(bool *hidden, const char *path); /** * Removes any trailing backslashes from a path, except in the case of a drive @@ -66,21 +76,27 @@ size_t git_win32__path_trim_end(wchar_t *str, size_t len); size_t git_win32__canonicalize_path(wchar_t *str, size_t len); /** - * Converts a FILETIME structure to a time_t. + * Converts a FILETIME structure to a struct timespec. * * @param FILETIME A pointer to a FILETIME - * @return A time_t containing the same time + * @param ts A pointer to the timespec structure to fill in */ -GIT_INLINE(time_t) git_win32__filetime_to_time_t(const FILETIME *ft) +GIT_INLINE(void) git_win32__filetime_to_timespec( + const FILETIME *ft, + struct timespec *ts) { long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ - winTime /= 10000000; /* Nano to seconds resolution */ - return (time_t)winTime; + ts->tv_sec = (time_t)(winTime / 10000000); +#ifdef GIT_USE_NSEC + ts->tv_nsec = (winTime % 10000000) * 100; +#else + ts->tv_nsec = 0; +#endif } GIT_INLINE(void) git_win32__timeval_to_filetime( - FILETIME *ft, const struct timeval tv) + FILETIME *ft, const struct p_timeval tv) { long long ticks = (tv.tv_sec * 10000000LL) + (tv.tv_usec * 10LL) + 116444736000000000LL; @@ -89,19 +105,25 @@ GIT_INLINE(void) git_win32__timeval_to_filetime( ft->dwLowDateTime = (ticks & 0xffffffffLL); } -GIT_INLINE(int) git_win32__file_attribute_to_stat( +GIT_INLINE(void) git_win32__stat_init( struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path) + DWORD dwFileAttributes, + DWORD nFileSizeHigh, + DWORD nFileSizeLow, + FILETIME ftCreationTime, + FILETIME ftLastAccessTime, + FILETIME ftLastWriteTime) { mode_t mode = S_IREAD; - if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + memset(st, 0, sizeof(struct stat)); + + if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) mode |= S_IFDIR; else mode |= S_IFREG; - if ((attrdata->dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) mode |= S_IWRITE; st->st_ino = 0; @@ -109,12 +131,39 @@ GIT_INLINE(int) git_win32__file_attribute_to_stat( st->st_uid = 0; st->st_nlink = 1; st->st_mode = mode; - st->st_size = ((git_off_t)attrdata->nFileSizeHigh << 32) + attrdata->nFileSizeLow; + st->st_size = ((git_off_t)nFileSizeHigh << 32) + nFileSizeLow; st->st_dev = _getdrive() - 1; st->st_rdev = st->st_dev; - st->st_atime = git_win32__filetime_to_time_t(&(attrdata->ftLastAccessTime)); - st->st_mtime = git_win32__filetime_to_time_t(&(attrdata->ftLastWriteTime)); - st->st_ctime = git_win32__filetime_to_time_t(&(attrdata->ftCreationTime)); + git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); + git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); + git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); +} + +GIT_INLINE(void) git_win32__file_information_to_stat( + struct stat *st, + const BY_HANDLE_FILE_INFORMATION *fileinfo) +{ + git_win32__stat_init(st, + fileinfo->dwFileAttributes, + fileinfo->nFileSizeHigh, + fileinfo->nFileSizeLow, + fileinfo->ftCreationTime, + fileinfo->ftLastAccessTime, + fileinfo->ftLastWriteTime); +} + +GIT_INLINE(int) git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path) +{ + git_win32__stat_init(st, + attrdata->dwFileAttributes, + attrdata->nFileSizeHigh, + attrdata->nFileSizeLow, + attrdata->ftCreationTime, + attrdata->ftLastAccessTime, + attrdata->ftLastWriteTime); if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { git_win32_path target; @@ -123,7 +172,7 @@ GIT_INLINE(int) git_win32__file_attribute_to_stat( st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; /* st_size gets the UTF-8 length of the target name, in bytes, - * not counting the NULL terminator */ + * not counting the NULL terminator */ if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { giterr_set(GITERR_OS, "Could not convert reparse point name for '%s'", path); return -1; diff --git a/src/win32/win32-compat.h b/src/win32/win32-compat.h new file mode 100644 index 000000000..f888fd69e --- /dev/null +++ b/src/win32/win32-compat.h @@ -0,0 +1,52 @@ +/* + * 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_win32_compat__ +#define INCLUDE_win32_compat__ + +#include +#include +#include +#include +#include + +typedef long suseconds_t; + +struct p_timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; + +struct p_timespec { + time_t tv_sec; + long tv_nsec; +}; + +#define timespec p_timespec + +struct p_stat { + _dev_t st_dev; + _ino_t st_ino; + mode_t st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec +#define st_atime_nsec st_atim.tv_nsec +#define st_mtime_nsec st_mtim.tv_nsec +#define st_ctime_nsec st_ctim.tv_nsec +}; + +#define stat p_stat + +#endif /* INCLUDE_win32_compat__ */ diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h index e2f1e892b..f08f72e16 100644 --- a/src/xdiff/xdiff.h +++ b/src/xdiff/xdiff.h @@ -20,6 +20,8 @@ * */ +#include "../util.h" + #if !defined(XDIFF_H) #define XDIFF_H @@ -106,9 +108,9 @@ typedef struct s_bdiffparam { } bdiffparam_t; -#define xdl_malloc(x) malloc(x) -#define xdl_free(ptr) free(ptr) -#define xdl_realloc(ptr,x) realloc(ptr,x) +#define xdl_malloc(x) git__malloc(x) +#define xdl_free(ptr) git__free(ptr) +#define xdl_realloc(ptr,x) git__realloc(ptr,x) void *xdl_mmfile_first(mmfile_t *mmf, long *size); long xdl_mmfile_size(mmfile_t *mmf); diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c index 0620e5fff..f4d01b48c 100644 --- a/src/xdiff/xdiffi.c +++ b/src/xdiff/xdiffi.c @@ -21,7 +21,8 @@ */ #include "xinclude.h" - +#include "common.h" +#include "integer.h" #define XDL_MAX_COST_MIN 256 @@ -323,7 +324,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe) { - long ndiags; + size_t ndiags, allocsize; long *kvd, *kvdf, *kvdb; xdalgoenv_t xenv; diffdata_t dd1, dd2; @@ -343,9 +344,12 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, * Allocate and setup K vectors to be used by the differential algorithm. * One is to store the forward path and one to store the backward path. */ - ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; - if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + GITERR_CHECK_ALLOC_ADD3(&ndiags, xe->xdf1.nreff, xe->xdf2.nreff, 3); + GITERR_CHECK_ALLOC_MULTIPLY(&allocsize, ndiags, 2); + GITERR_CHECK_ALLOC_ADD(&allocsize, allocsize, 2); + GITERR_CHECK_ALLOC_MULTIPLY(&allocsize, allocsize, sizeof(long)); + if (!(kvd = (long *) xdl_malloc(allocsize))) { xdl_free_env(xe); return -1; } diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c index 500d8112a..0c2edb89c 100644 --- a/src/xdiff/xhistogram.c +++ b/src/xdiff/xhistogram.c @@ -44,6 +44,7 @@ #include "xinclude.h" #include "xtypes.h" #include "xdiff.h" +#include "common.h" #define MAX_PTR UINT_MAX #define MAX_CNT UINT_MAX @@ -271,7 +272,7 @@ static int histogram_diff( { struct histindex index; struct region lcs; - unsigned int sz; + size_t sz; int result = -1; if (count1 <= 0 && count2 <= 0) @@ -302,7 +303,8 @@ static int histogram_diff( index.table_bits = xdl_hashbits(count1); sz = index.records_size = 1 << index.table_bits; - sz *= sizeof(struct record *); + GITERR_CHECK_ALLOC_MULTIPLY(&sz, sz, sizeof(struct record *)); + if (!(index.records = (struct record **) xdl_malloc(sz))) goto cleanup; memset(index.records, 0, sz); diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c index b11e59817..6448b5542 100644 --- a/src/xdiff/xmerge.c +++ b/src/xdiff/xmerge.c @@ -21,6 +21,7 @@ */ #include "xinclude.h" +#include "common.h" typedef struct s_xdmerge { struct s_xdmerge *next; @@ -109,59 +110,74 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, return 0; } -static int xdl_recs_copy_0(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 add_nl, char *dest) { xrecord_t **recs; - int size = 0; + size_t size = 0; + + *out = 0; recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; if (count < 1) return 0; - for (i = 0; i < count; size += recs[i++]->size) + for (i = 0; i < count; ) { if (dest) memcpy(dest + size, recs[i]->ptr, recs[i]->size); + + GITERR_CHECK_ALLOC_ADD(&size, size, recs[i++]->size); + } + if (add_nl) { i = recs[count - 1]->size; if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { if (dest) dest[size] = '\n'; - size++; + + GITERR_CHECK_ALLOC_ADD(&size, size, 1); } } - return size; + + *out = size; + return 0; } -static int xdl_recs_copy(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 add_nl, char *dest) { - return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(out, 0, xe, i, count, add_nl, dest); } -static int xdl_orig_copy(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 add_nl, char *dest) { - return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(out, 1, xe, i, count, add_nl, dest); } -static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, +static int fill_conflict_hunk(size_t *out, xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, const char *name3, - int size, int i, int style, + size_t size, int i, int style, xdmerge_t *m, char *dest, int marker_size) { 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); + size_t copied; + + *out = 0; if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, - dest ? dest + size : NULL); + if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL) < 0) + return -1; + + GITERR_CHECK_ALLOC_ADD(&size, size, copied); if (!dest) { - size += marker_size + 1 + marker1_size; + GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker1_size); } else { memset(dest + size, '<', marker_size); size += marker_size; @@ -174,13 +190,16 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } /* Postimage from side #1 */ - size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, - dest ? dest + size : NULL); + if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL) < 0) + return -1; + + GITERR_CHECK_ALLOC_ADD(&size, size, copied); if (style == XDL_MERGE_DIFF3) { /* Shared preimage */ if (!dest) { - size += marker_size + 1 + marker3_size; + GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker3_size); } else { memset(dest + size, '|', marker_size); size += marker_size; @@ -191,12 +210,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } dest[size++] = '\n'; } - size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, - dest ? dest + size : NULL); + + if (xdl_orig_copy(&copied, xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); } if (!dest) { - size += marker_size + 1; + GITERR_CHECK_ALLOC_ADD3(&size, size, marker_size, 1); } else { memset(dest + size, '=', marker_size); size += marker_size; @@ -204,10 +226,14 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } /* Postimage from side #2 */ - size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, - dest ? dest + size : NULL); + + if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); + if (!dest) { - size += marker_size + 1 + marker2_size; + GITERR_CHECK_ALLOC_ADD4(&size, size, marker_size, 1, marker2_size); } else { memset(dest + size, '>', marker_size); size += marker_size; @@ -218,46 +244,69 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } dest[size++] = '\n'; } - return size; + + *out = size; + return 0; } -static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, +static int xdl_fill_merge_buffer(size_t *out, + xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, const char *ancestor_name, int favor, xdmerge_t *m, char *dest, int style, int marker_size) { - int size, i; + size_t size, copied; + int i; + + *out = 0; for (size = i = 0; m; m = m->next) { if (favor && !m->mode) m->mode = favor; - if (m->mode == 0) - size = fill_conflict_hunk(xe1, name1, xe2, name2, + if (m->mode == 0) { + if (fill_conflict_hunk(&size, xe1, name1, xe2, name2, ancestor_name, size, i, style, m, dest, - marker_size); + marker_size) < 0) + return -1; + } else if (m->mode & 3) { /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, - dest ? dest + size : NULL); + if (xdl_recs_copy(&copied, xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); + /* Postimage from side #1 */ - if (m->mode & 1) - size += xdl_recs_copy(xe1, m->i1, m->chg1, (m->mode & 2), - dest ? dest + size : NULL); + if (m->mode & 1) { + if (xdl_recs_copy(&copied, xe1, m->i1, m->chg1, (m->mode & 2), + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); + } + /* Postimage from side #2 */ - if (m->mode & 2) - size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, - dest ? dest + size : NULL); + if (m->mode & 2) { + if (xdl_recs_copy(&copied, xe2, m->i2, m->chg2, 0, + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); + } } else continue; i = m->i1 + m->chg1; } - size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, - dest ? dest + size : NULL); - return size; + + if (xdl_recs_copy(&copied, xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL) < 0) + return -1; + GITERR_CHECK_ALLOC_ADD(&size, size, copied); + + *out = size; + return 0; } /* @@ -551,19 +600,24 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, /* output */ if (result) { int marker_size = xmp->marker_size; - int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + size_t size; + + if (xdl_fill_merge_buffer(&size, xe1, name1, xe2, name2, ancestor_name, favor, changes, NULL, style, - marker_size); + marker_size) < 0) + return -1; + result->ptr = xdl_malloc(size); if (!result->ptr) { xdl_cleanup_merge(changes); return -1; } result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, + if (xdl_fill_merge_buffer(&size, xe1, name1, xe2, name2, ancestor_name, favor, changes, - result->ptr, style, marker_size); + result->ptr, style, marker_size) < 0) + return -1; } return xdl_cleanup_merge(changes); } @@ -579,8 +633,11 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, result->ptr = NULL; result->size = 0; - if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || - xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) { + return -1; + } + if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + xdl_free_env(&xe1); return -1; } if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || @@ -592,6 +649,8 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_script(xscr1); + xdl_free_env(&xe1); xdl_free_env(&xe2); return -1; } diff --git a/tests/blame/blame_helpers.c b/tests/blame/blame_helpers.c index b305ba1e3..61e87350c 100644 --- a/tests/blame/blame_helpers.c +++ b/tests/blame/blame_helpers.c @@ -4,7 +4,7 @@ void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) { va_list arglist; - printf("Hunk %"PRIuZ" (line %d +%d): ", idx, + printf("Hunk %"PRIuZ" (line %"PRIuZ" +%"PRIuZ"): ", idx, hunk->final_start_line_number, hunk->lines_in_hunk-1); va_start(arglist, fmt); @@ -15,7 +15,7 @@ void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) } void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx, - int start_line, int len, char boundary, const char *commit_id, const char *orig_path) + size_t start_line, size_t len, char boundary, const char *commit_id, const char *orig_path) { char expected[GIT_OID_HEXSZ+1] = {0}, actual[GIT_OID_HEXSZ+1] = {0}; const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx); diff --git a/tests/blame/blame_helpers.h b/tests/blame/blame_helpers.h index 94321a5b5..fd5a35d2c 100644 --- a/tests/blame/blame_helpers.h +++ b/tests/blame/blame_helpers.h @@ -7,10 +7,8 @@ void check_blame_hunk_index( git_repository *repo, git_blame *blame, int idx, - int start_line, - int len, + size_t start_line, + size_t len, char boundary, const char *commit_id, const char *orig_path); - - diff --git a/tests/blame/simple.c b/tests/blame/simple.c index 83e5e056b..30b78168f 100644 --- a/tests/blame/simple.c +++ b/tests/blame/simple.c @@ -281,6 +281,18 @@ void test_blame_simple__can_restrict_lines_both(void) check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt"); } +void test_blame_simple__can_blame_huge_file(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "huge.txt", &opts)); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 65536, 0, "4eecfea", "huge.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 65537, 1, 0, "6653ff4", "huge.txt"); +} + /* * $ git blame -n branch_file.txt be3563a..HEAD * orig line no final line no diff --git a/tests/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c index 92a454d12..d7d24f33f 100644 --- a/tests/checkout/checkout_helpers.c +++ b/tests/checkout/checkout_helpers.c @@ -132,8 +132,8 @@ int checkout_count_callback( void tick_index(git_index *index) { - git_time_t ts; - struct timeval times[2]; + struct timespec ts; + struct p_timeval times[2]; cl_assert(index->on_disk); cl_assert(git_index_path(index)); @@ -141,10 +141,10 @@ void tick_index(git_index *index) cl_git_pass(git_index_read(index, true)); ts = index->stamp.mtime; - times[0].tv_sec = ts; - times[0].tv_usec = 0; - times[1].tv_sec = ts + 5; - times[1].tv_usec = 0; + times[0].tv_sec = ts.tv_sec; + times[0].tv_usec = ts.tv_nsec / 1000; + times[1].tv_sec = ts.tv_sec + 5; + times[1].tv_usec = ts.tv_nsec / 1000; cl_git_pass(p_utimes(git_index_path(index), times)); cl_git_pass(git_index_read(index, true)); diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c index 8e77d0845..d467eaadd 100644 --- a/tests/checkout/crlf.c +++ b/tests/checkout/crlf.c @@ -278,6 +278,7 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) void test_checkout_crlf__with_ident(void) { git_index *index; + const git_index_entry *entry; git_blob *blob; git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; opts.checkout_strategy = GIT_CHECKOUT_FORCE; @@ -310,14 +311,14 @@ void test_checkout_crlf__with_ident(void) /* check that blobs have $Id$ */ - cl_git_pass(git_blob_lookup(&blob, g_repo, - & git_index_get_bypath(index, "lf.ident", 0)->id)); + cl_assert((entry = git_index_get_bypath(index, "lf.ident", 0))); + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); cl_assert_equal_s( ALL_LF_TEXT_RAW "\n$Id$\n", git_blob_rawcontent(blob)); git_blob_free(blob); - cl_git_pass(git_blob_lookup(&blob, g_repo, - & git_index_get_bypath(index, "more2.identcrlf", 0)->id)); + cl_assert((entry = git_index_get_bypath(index, "more2.identcrlf", 0))); + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); cl_assert_equal_s( "\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob)); git_blob_free(blob); diff --git a/tests/checkout/index.c b/tests/checkout/index.c index 0d220e141..8af3e5684 100644 --- a/tests/checkout/index.c +++ b/tests/checkout/index.c @@ -63,7 +63,7 @@ void test_checkout_index__can_remove_untracked_files(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", 0755, GIT_MKDIR_PATH); cl_git_mkfile("./testrepo/dir/one", "one\n"); cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); @@ -298,7 +298,7 @@ void test_checkout_index__options_dir_modes(void) /* File-mode test, since we're on the 'dir' branch */ cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_BLOB_EXECUTABLE, "%07o"); + cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_BLOB_EXECUTABLE & ~um, "%07o"); git_commit_free(commit); } diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index be4019822..5680b86df 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -946,7 +946,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0)); - cl_assert_equal_i(0100755, entry->mode); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -957,7 +957,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert_equal_i(0100644, entry->mode); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -968,7 +968,18 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert_equal_i(0100755, entry->mode); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); + + git_commit_free(commit); + + + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -976,6 +987,73 @@ void test_checkout_tree__filemode_preserved_in_index(void) git_index_free(index); } +mode_t read_filemode(const char *path) +{ + git_buf fullpath = GIT_BUF_INIT; + struct stat st; + mode_t result; + + git_buf_joinpath(&fullpath, "testrepo", path); + cl_must_pass(p_stat(fullpath.ptr, &st)); + + result = GIT_PERMS_IS_EXEC(st.st_mode) ? + GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB; + + git_buf_free(&fullpath); + + return result; +} + +void test_checkout_tree__filemode_preserved_in_workdir(void) +{ +#ifndef GIT_WIN32 + git_oid executable_oid; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* test a freshly added executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt"))); + + git_commit_free(commit); + + + /* Now start with a commit which has a text file */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); + + + /* And then check out to a commit which converts the text file to an executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); + + + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); +#endif +} + void test_checkout_tree__removes_conflicts(void) { git_oid commit_id; diff --git a/tests/cherrypick/workdir.c b/tests/cherrypick/workdir.c index 787f1f4d4..2b45f5a33 100644 --- a/tests/cherrypick/workdir.c +++ b/tests/cherrypick/workdir.c @@ -300,7 +300,7 @@ void test_cherrypick_workdir__rename(void) { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" }, }; - opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; opts.merge_opts.rename_threshold = 50; git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); @@ -335,7 +335,7 @@ void test_cherrypick_workdir__both_renamed(void) { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" }, }; - opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; opts.merge_opts.rename_threshold = 50; git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70"); diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index b14af44a9..314d3441e 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -58,31 +58,38 @@ void cl_git_rmfile(const char *filename) cl_must_pass(p_unlink(filename)); } +char *cl_getenv(const char *name) +{ + git_buf out = GIT_BUF_INIT; + int error = git__getenv(&out, name); + + cl_assert(error >= 0 || error == GIT_ENOTFOUND); + + if (error == GIT_ENOTFOUND) + return NULL; + + if (out.size == 0) { + char *dup = git__strdup(""); + cl_assert(dup); + + return dup; + } + + return git_buf_detach(&out); +} + +bool cl_is_env_set(const char *name) +{ + char *env = cl_getenv(name); + bool result = (env != NULL); + git__free(env); + return result; +} + #ifdef GIT_WIN32 #include "win32/utf-conv.h" -char *cl_getenv(const char *name) -{ - wchar_t *wide_name, *wide_value; - char *utf8_value = NULL; - DWORD value_len; - - cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); - - value_len = GetEnvironmentVariableW(wide_name, NULL, 0); - - if (value_len) { - cl_assert(wide_value = git__malloc(value_len * sizeof(wchar_t))); - cl_assert(GetEnvironmentVariableW(wide_name, wide_value, value_len)); - cl_assert(git__utf16_to_8_alloc(&utf8_value, wide_value) >= 0); - git__free(wide_value); - } - - git__free(wide_name); - return utf8_value; -} - int cl_setenv(const char *name, const char *value) { wchar_t *wide_name, *wide_value = NULL; @@ -138,10 +145,6 @@ int cl_rename(const char *source, const char *dest) #else #include -char *cl_getenv(const char *name) -{ - return getenv(name); -} int cl_setenv(const char *name, const char *value) { @@ -296,6 +299,8 @@ const char* cl_git_path_url(const char *path) in_buf++; } + cl_assert(url_buf.size < 4096); + strncpy(url, git_buf_cstr(&url_buf), 4096); git_buf_free(&url_buf); git_buf_free(&path_buf); @@ -536,14 +541,23 @@ void cl_fake_home(void) void cl_sandbox_set_search_path_defaults(void) { - const char *sandbox_path = clar_sandbox_path(); + git_buf path = GIT_BUF_INIT; + + git_buf_joinpath(&path, clar_sandbox_path(), "__config"); + + if (!git_path_exists(path.ptr)) + cl_must_pass(p_mkdir(path.ptr, 0777)); git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path); + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr); git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path); + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr); git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, sandbox_path); + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr); + + git_buf_free(&path); } #ifdef GIT_WIN32 diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index 9ab0da4f6..d7e635302 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -119,6 +119,7 @@ bool cl_is_chmod_supported(void); /* Environment wrappers */ char *cl_getenv(const char *name); +bool cl_is_env_set(const char *name); int cl_setenv(const char *name, const char *value); /* Reliable rename */ diff --git a/tests/clar_libgit2_trace.c b/tests/clar_libgit2_trace.c index ae582d1cb..aaeeb7810 100644 --- a/tests/clar_libgit2_trace.c +++ b/tests/clar_libgit2_trace.c @@ -142,9 +142,28 @@ void _cl_trace_cb__event_handler( switch (ev) { case CL_TRACE__SUITE_BEGIN: git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); +#if 0 && defined(GIT_MSVC_CRTDBG) + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, + suite_name); +#endif break; case CL_TRACE__SUITE_END: +#if 0 && defined(GIT_MSVC_CRTDBG) + /* As an example of checkpointing, dump leaks within this suite. + * This may generate false positives for things like the global + * TLS error state and maybe the odb cache since they aren't + * freed until the global shutdown and outside the scope of this + * set of tests. + * + * This may under-report if the test itself uses a checkpoint. + * See tests/trace/windows/stacktrace.c + */ + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + suite_name); +#endif git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR); break; diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c index 44a503818..7ebf19f46 100644 --- a/tests/clone/nonetwork.c +++ b/tests/clone/nonetwork.c @@ -297,16 +297,19 @@ static void assert_correct_reflog(const char *name) { git_reflog *log; const git_reflog_entry *entry; - char expected_log_message[128] = {0}; + git_buf expected_message = GIT_BUF_INIT; - sprintf(expected_log_message, "clone: from %s", cl_git_fixture_url("testrepo.git")); + git_buf_printf(&expected_message, + "clone: from %s", cl_git_fixture_url("testrepo.git")); cl_git_pass(git_reflog_read(&log, g_repo, name)); cl_assert_equal_i(1, git_reflog_entrycount(log)); entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s(expected_log_message, git_reflog_entry_message(entry)); + cl_assert_equal_s(expected_message.ptr, git_reflog_entry_message(entry)); git_reflog_free(log); + + git_buf_free(&expected_message); } void test_clone_nonetwork__clone_updates_reflog_properly(void) diff --git a/tests/commit/commit.c b/tests/commit/commit.c index f5461cfd3..c052cd568 100644 --- a/tests/commit/commit.c +++ b/tests/commit/commit.c @@ -63,6 +63,18 @@ void assert_commit_summary(const char *expected, const char *given) git_commit__free(dummy); } +void assert_commit_body(const char *expected, const char *given) +{ + git_commit *dummy; + + cl_assert(dummy = git__calloc(1, sizeof(struct git_commit))); + + dummy->raw_message = git__strdup(given); + cl_assert_equal_s(expected, git_commit_body(dummy)); + + git_commit__free(dummy); +} + void test_commit_commit__summary(void) { assert_commit_summary("One-liner with no trailing newline", "One-liner with no trailing newline"); @@ -70,14 +82,45 @@ void test_commit_commit__summary(void) assert_commit_summary("Trimmed leading&trailing newlines", "\n\nTrimmed leading&trailing newlines\n\n"); assert_commit_summary("First paragraph only", "\nFirst paragraph only\n\n(There are more!)"); assert_commit_summary("First paragraph with unwrapped trailing\tlines", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); - assert_commit_summary("\tLeading \ttabs", "\tLeading\n\ttabs\n\nis preserved"); - assert_commit_summary(" Leading Spaces", " Leading\n Spaces\n\nare preserved"); + assert_commit_summary("\tLeading tabs", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ + assert_commit_summary(" Leading Spaces", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ assert_commit_summary("Trailing tabs\tare removed", "Trailing tabs\tare removed\t\t"); assert_commit_summary("Trailing spaces are removed", "Trailing spaces are removed "); assert_commit_summary("Trailing tabs", "Trailing tabs\t\n\nare removed"); assert_commit_summary("Trailing spaces", "Trailing spaces \n\nare removed"); + assert_commit_summary("Newlines are replaced by spaces", "Newlines\nare\nreplaced by spaces\n"); + assert_commit_summary(" Spaces after newlines are collapsed", "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ + assert_commit_summary(" Spaces before newlines are collapsed", " \nSpaces before newlines \nare \ncollapsed \n"); + assert_commit_summary(" Spaces around newlines are collapsed", " \n Spaces around newlines \n are \n collapsed \n "); + assert_commit_summary(" Trailing newlines are" , " \n Trailing newlines \n are \n\n collapsed \n "); + assert_commit_summary(" Trailing spaces are stripped", " \n Trailing spaces \n are stripped \n\n \n \t "); assert_commit_summary("", ""); assert_commit_summary("", " "); assert_commit_summary("", "\n"); assert_commit_summary("", "\n \n"); } + +void test_commit_commit__body(void) +{ + assert_commit_body(NULL, "One-liner with no trailing newline"); + assert_commit_body(NULL, "One-liner with trailing newline\n"); + assert_commit_body(NULL, "\n\nTrimmed leading&trailing newlines\n\n"); + assert_commit_body("(There are more!)", "\nFirst paragraph only\n\n(There are more!)"); + assert_commit_body("(Yes, unwrapped!)", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); + assert_commit_body("are preserved", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ + assert_commit_body("are preserved", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ + assert_commit_body(NULL, "Trailing tabs\tare removed\t\t"); + assert_commit_body(NULL, "Trailing spaces are removed "); + assert_commit_body("are removed", "Trailing tabs\t\n\nare removed"); + assert_commit_body("are removed", "Trailing spaces \n\nare removed"); + assert_commit_body(NULL,"Newlines\nare\nreplaced by spaces\n"); + assert_commit_body(NULL , "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ + assert_commit_body(NULL , " \nSpaces before newlines \nare \ncollapsed \n"); + assert_commit_body(NULL , " \n Spaces around newlines \n are \n collapsed \n "); + assert_commit_body("collapsed" , " \n Trailing newlines \n are \n\n collapsed \n "); + assert_commit_body(NULL, " \n Trailing spaces \n are stripped \n\n \n \t "); + assert_commit_body(NULL , ""); + assert_commit_body(NULL , " "); + assert_commit_body(NULL , "\n"); + assert_commit_body(NULL , "\n \n"); +} diff --git a/tests/commit/parse.c b/tests/commit/parse.c index 388da078a..838cfb467 100644 --- a/tests/commit/parse.c +++ b/tests/commit/parse.c @@ -443,16 +443,87 @@ cpxtDQQMGYFpXK/71stq\n\ cl_git_pass(parse_commit(&commit, passing_commit_cases[4])); + cl_git_pass(git_commit_header_field(&buf, commit, "tree")); + cl_assert_equal_s("6b79e22d69bf46e289df0345a14ca059dfc9bdf6", buf.ptr); + git_buf_clear(&buf); + cl_git_pass(git_commit_header_field(&buf, commit, "parent")); cl_assert_equal_s("34734e478d6cf50c27c9d69026d93974d052c454", buf.ptr); git_buf_clear(&buf); cl_git_pass(git_commit_header_field(&buf, commit, "gpgsig")); cl_assert_equal_s(gpgsig, buf.ptr); + git_buf_clear(&buf); cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "awesomeness")); cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "par")); + git_commit__free(commit); + cl_git_pass(parse_commit(&commit, passing_commit_cases[0])); + + cl_git_pass(git_commit_header_field(&buf, commit, "committer")); + cl_assert_equal_s("Vicent Marti 1273848544 +0200", buf.ptr); + git_buf_free(&buf); git_commit__free(commit); } + +void test_commit_parse__extract_signature(void) +{ + git_odb *odb; + git_oid commit_id; + git_buf signature = GIT_BUF_INIT, signed_data = GIT_BUF_INIT; + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + + + cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); + cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[4], strlen(passing_commit_cases[4]), GIT_OBJ_COMMIT)); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + git_buf_clear(&signature); + git_buf_clear(&signed_data); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, "gpgsig")); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + /* Try to parse a tree */ + cl_git_pass(git_oid_fromstr(&commit_id, "45dd856fdd4d89b884c340ba0e047752d9b085d6")); + cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_i(GITERR_INVALID, giterr_last()->klass); + + /* Try to parse an unsigned commit */ + cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[1], strlen(passing_commit_cases[1]), GIT_OBJ_COMMIT)); + cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_i(GITERR_OBJECT, giterr_last()->klass); + + git_buf_free(&signature); + git_buf_free(&signed_data); + +} diff --git a/tests/commit/signature.c b/tests/commit/signature.c index 41a74b999..0070320ae 100644 --- a/tests/commit/signature.c +++ b/tests/commit/signature.c @@ -35,6 +35,13 @@ void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n"); } +void test_commit_signature__leading_and_trailing_crud_is_trimmed(void) +{ + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", "\"nulltoken\"", "\"emeric.fermas@gmail.com\""); + assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w.", "emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com"); +} + void test_commit_signature__angle_brackets_in_names_are_not_supported(void) { cl_git_fail(try_build_signature("value); + git_config_entry_free(entry); + cl_git_pass(git_config_lock(&tx, cfg)); + + /* Change entries in the locked backend */ + cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); + cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); + + /* We can see that the file we read from hasn't changed */ + cl_git_pass(git_config_open_ondisk(&cfg2, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg2, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg2, "section2.name3")); + git_config_free(cfg2); + + /* And we also get the old view when we read from the locked config */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); + + cl_git_pass(git_transaction_commit(tx)); + git_transaction_free(tx); + + /* Now that we've unlocked it, we should see both updates */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("other value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); + cl_assert_equal_s("more value", entry->value); + git_config_entry_free(entry); + + git_config_free(cfg); + + /* We should also see the changes after reopening the config */ + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("other value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); + cl_assert_equal_s("more value", entry->value); + git_config_entry_free(entry); + + git_config_free(cfg); +} diff --git a/tests/core/buffer.c b/tests/core/buffer.c index 0e7026a9c..9872af7f4 100644 --- a/tests/core/buffer.c +++ b/tests/core/buffer.c @@ -929,7 +929,7 @@ void test_core_buffer__similarity_metric(void) cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1)); cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_futils_mkdir("scratch", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); cl_git_pass(git_hashsig_create_fromfile( &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); diff --git a/tests/core/copy.c b/tests/core/copy.c index 04b2dfab5..967748cc5 100644 --- a/tests/core/copy.c +++ b/tests/core/copy.c @@ -25,7 +25,7 @@ void test_core_copy__file_in_dir(void) struct stat st; const char *content = "This is some other stuff to copy\n"; - cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); cl_git_mkfile("an_dir/in_a_dir/copy_me", content); cl_assert(git_path_isdir("an_dir")); @@ -60,9 +60,9 @@ void test_core_copy__tree(void) struct stat st; const char *content = "File content\n"; - cl_git_pass(git_futils_mkdir("src/b", NULL, 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/d", NULL, 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/e", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); cl_git_mkfile("src/f1", content); cl_git_mkfile("src/b/f2", content); diff --git a/tests/core/dirent.c b/tests/core/dirent.c index d95e44196..2bd60269d 100644 --- a/tests/core/dirent.c +++ b/tests/core/dirent.c @@ -275,3 +275,32 @@ void test_core_dirent__diriter_with_fullname(void) check_counts(&sub); } + +void test_core_dirent__diriter_at_directory_root(void) +{ + git_path_diriter diriter = GIT_PATH_DIRITER_INIT; + const char *sandbox_path, *path; + char *root_path; + size_t path_len; + int root_offset, error; + + sandbox_path = clar_sandbox_path(); + cl_assert((root_offset = git_path_root(sandbox_path)) >= 0); + + cl_assert(root_path = git__calloc(1, root_offset + 2)); + strncpy(root_path, sandbox_path, root_offset + 1); + + cl_git_pass(git_path_diriter_init(&diriter, root_path, 0)); + + while ((error = git_path_diriter_next(&diriter)) == 0) { + cl_git_pass(git_path_diriter_fullpath(&path, &path_len, &diriter)); + + cl_assert(path_len > (size_t)(root_offset + 1)); + cl_assert(path[root_offset+1] != '/'); + } + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_path_diriter_free(&diriter); + git__free(root_path); +} diff --git a/tests/core/env.c b/tests/core/env.c index 293b786db..ee08258a6 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -30,14 +30,8 @@ static char *home_values[] = { void test_core_env__initialize(void) { int i; - for (i = 0; i < NUM_VARS; ++i) { - const char *original = cl_getenv(env_vars[i]); -#ifdef GIT_WIN32 - env_save[i] = (char *)original; -#else - env_save[i] = original ? git__strdup(original) : NULL; -#endif - } + for (i = 0; i < NUM_VARS; ++i) + env_save[i] = cl_getenv(env_vars[i]); } static void set_global_search_path_from_env(void) @@ -77,12 +71,14 @@ static void setenv_and_check(const char *name, const char *value) char *check; cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - cl_assert_equal_s(value, check); -#ifdef GIT_WIN32 + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + git__free(check); -#endif } void test_core_env__0(void) diff --git a/tests/core/errors.c b/tests/core/errors.c index a06ec4abc..ab18951a6 100644 --- a/tests/core/errors.c +++ b/tests/core/errors.c @@ -93,23 +93,69 @@ void test_core_errors__restore(void) giterr_clear(); cl_assert(giterr_last() == NULL); - cl_assert_equal_i(0, giterr_capture(&err_state, 0)); + cl_assert_equal_i(0, giterr_state_capture(&err_state, 0)); memset(&err_state, 0x0, sizeof(git_error_state)); giterr_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, giterr_capture(&err_state, -1)); + cl_assert_equal_i(-1, giterr_state_capture(&err_state, -1)); cl_assert(giterr_last() == NULL); giterr_set(99, "Bar: %s", "foo"); - giterr_restore(&err_state); + giterr_state_restore(&err_state); cl_assert_equal_i(42, giterr_last()->klass); cl_assert_equal_s("Foo: bar", giterr_last()->message); } +void test_core_errors__free_state(void) +{ + git_error_state err_state = {0}; + + giterr_clear(); + + giterr_set(42, "Foo: %s", "bar"); + cl_assert_equal_i(-1, giterr_state_capture(&err_state, -1)); + + giterr_set(99, "Bar: %s", "foo"); + + giterr_state_free(&err_state); + + cl_assert_equal_i(99, giterr_last()->klass); + cl_assert_equal_s("Bar: foo", giterr_last()->message); + + giterr_state_restore(&err_state); + + cl_assert(giterr_last() == NULL); +} + +void test_core_errors__restore_oom(void) +{ + git_error_state err_state = {0}; + const git_error *oom_error = NULL; + + giterr_clear(); + + giterr_set_oom(); /* internal fn */ + oom_error = giterr_last(); + cl_assert(oom_error); + + cl_assert_equal_i(-1, giterr_state_capture(&err_state, -1)); + + cl_assert(giterr_last() == NULL); + cl_assert_equal_i(GITERR_NOMEMORY, err_state.error_msg.klass); + cl_assert_equal_s("Out of memory", err_state.error_msg.message); + + giterr_state_restore(&err_state); + + cl_assert(giterr_last()->klass == GITERR_NOMEMORY); + cl_assert_(giterr_last() == oom_error, "static oom error not restored"); + + giterr_clear(); +} + static int test_arraysize_multiply(size_t nelem, size_t size) { size_t out; diff --git a/tests/core/features.c b/tests/core/features.c index 5eeb05e81..85cddfeff 100644 --- a/tests/core/features.c +++ b/tests/core/features.c @@ -28,4 +28,10 @@ void test_core_features__0(void) #else cl_assert((caps & GIT_FEATURE_SSH) == 0); #endif + +#if defined(GIT_USE_NSEC) + cl_assert((caps & GIT_FEATURE_NSEC) != 0); +#else + cl_assert((caps & GIT_FEATURE_NSEC) == 0); +#endif } diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c index 3f7dc8569..04a380b20 100644 --- a/tests/core/filebuf.c +++ b/tests/core/filebuf.c @@ -151,3 +151,91 @@ void test_core_filebuf__rename_error(void) cl_assert_equal_i(false, git_path_exists(test_lock)); } + +void test_core_filebuf__symlink_follow(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0777)); + cl_git_pass(p_symlink("target", source)); + + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + + /* The second time around, the target file does exist */ + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__symlink_depth(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0777)); + /* Endless loop */ + cl_git_pass(p_symlink("link", source)); + + cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); + + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "hidden", *test = "hidden/test"; + bool hidden; + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + + cl_git_pass(git_win32__set_hidden(test, true)); + cl_git_pass(git_win32__hidden(&hidden, test)); + cl_assert(hidden); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_git_pass(git_filebuf_commit(&file)); + + git_filebuf_cleanup(&file); +#endif +} + +void test_core_filebuf__detects_directory(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + + cl_must_pass(p_mkdir("foo", 0777)); + cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666)); + cl_must_pass(p_rmdir("foo")); +} diff --git a/tests/core/ftruncate.c b/tests/core/ftruncate.c index 21981d677..2f4729fc2 100644 --- a/tests/core/ftruncate.c +++ b/tests/core/ftruncate.c @@ -10,7 +10,7 @@ static int fd = -1; void test_core_ftruncate__initialize(void) { - if (!cl_getenv("GITTEST_INVASIVE_FS_SIZE")) + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) cl_skip(); cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644))); diff --git a/tests/core/futils.c b/tests/core/futils.c new file mode 100644 index 000000000..e7f7154ed --- /dev/null +++ b/tests/core/futils.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "fileops.h" + +// Fixture setup and teardown +void test_core_futils__initialize(void) +{ + cl_must_pass(p_mkdir("futils", 0777)); +} + +void test_core_futils__cleanup(void) +{ + cl_fixture_cleanup("futils"); +} + +void test_core_futils__writebuffer(void) +{ + git_buf out = GIT_BUF_INIT, + append = GIT_BUF_INIT; + + /* create a new file */ + git_buf_puts(&out, "hello!\n"); + git_buf_printf(&out, "this is a %s\n", "test"); + + cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + /* append some more data */ + git_buf_puts(&append, "And some more!\n"); + git_buf_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + git_buf_free(&out); + git_buf_free(&append); +} + +void test_core_futils__write_hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_buf out = GIT_BUF_INIT, append = GIT_BUF_INIT; + bool hidden; + + git_buf_puts(&out, "hidden file.\n"); + git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); + + cl_git_pass(git_win32__set_hidden("futils/test-file", true)); + + /* append some more data */ + git_buf_puts(&append, "And some more!\n"); + git_buf_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); + cl_assert(hidden); + + git_buf_free(&out); + git_buf_free(&append); +#endif +} + diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index f76fe1da9..96c972396 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -13,43 +13,82 @@ static void cleanup_basic_dirs(void *ref) git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } +void test_core_mkdir__absolute(void) +{ + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + git_buf_joinpath(&path, clar_sandbox_path(), "d0"); + + /* make a directory */ + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_path_isdir(path.ptr)); + + git_buf_joinpath(&path, path.ptr, "subdir"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_path_isdir(path.ptr)); + + /* ensure mkdir_r works for a single subdir */ + git_buf_joinpath(&path, path.ptr, "another"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_path_isdir(path.ptr)); + + /* ensure mkdir_r works */ + git_buf_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_path_isdir(path.ptr)); + + /* ensure we don't imply recursive */ + git_buf_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(!git_path_isdir(path.ptr)); + + git_buf_free(&path); +} + void test_core_mkdir__basic(void) { cl_set_cleanup(cleanup_basic_dirs, NULL); /* make a directory */ cl_assert(!git_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", NULL, 0755, 0)); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); cl_assert(git_path_isdir("d0")); /* make a path */ cl_assert(!git_path_isdir("d1")); - cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); cl_assert(git_path_isdir("d1")); cl_assert(git_path_isdir("d1/d1.1")); cl_assert(git_path_isdir("d1/d1.1/d1.2")); /* make a dir exclusively */ cl_assert(!git_path_isdir("d2")); - cl_git_pass(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); cl_assert(git_path_isdir("d2")); /* make exclusive failure */ - cl_git_fail(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); /* make a path exclusively */ cl_assert(!git_path_isdir("d3")); - cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); cl_assert(git_path_isdir("d3")); cl_assert(git_path_isdir("d3/d3.1/d3.2")); /* make exclusive path failure */ - cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); /* ??? Should EXCL only apply to the last item in the path? */ /* path with trailing slash? */ cl_assert(!git_path_isdir("d4")); - cl_git_pass(git_futils_mkdir("d4/d4.1/", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); cl_assert(git_path_isdir("d4/d4.1")); } @@ -65,38 +104,38 @@ void test_core_mkdir__with_base(void) cl_set_cleanup(cleanup_basedir, NULL); - cl_git_pass(git_futils_mkdir(BASEDIR, NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("a", BASEDIR, 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); cl_assert(git_path_isdir(BASEDIR "/a")); - cl_git_pass(git_futils_mkdir("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); cl_assert(git_path_isdir(BASEDIR "/b/b1/b2")); /* exclusive with existing base */ - cl_git_pass(git_futils_mkdir("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: exclusive with duplicated suffix */ - cl_git_fail(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: exclusive with any duplicated component */ - cl_git_fail(git_futils_mkdir("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* success: exclusive without path */ - cl_git_pass(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); /* path with shorter base and existing dirs */ - cl_git_pass(git_futils_mkdir("dir/here/d/", "base", 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); cl_assert(git_path_isdir("base/dir/here/d")); /* fail: path with shorter base and existing dirs */ - cl_git_fail(git_futils_mkdir("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: base with missing components */ - cl_git_fail(git_futils_mkdir("f/", "base/missing", 0755, GIT_MKDIR_PATH)); + cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); /* success: shift missing component to path */ - cl_git_pass(git_futils_mkdir("missing/f/", "base/", 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); } static void cleanup_chmod_root(void *ref) @@ -135,9 +174,9 @@ void test_core_mkdir__chmods(void) cl_set_cleanup(cleanup_chmod_root, old); - cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0)); + cl_git_pass(git_futils_mkdir("r", 0777, 0)); - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -146,7 +185,7 @@ void test_core_mkdir__chmods(void) cl_git_pass(git_path_lstat("r/mode/is/important", &st)); check_mode(0755, st.st_mode); - cl_git_pass(git_futils_mkdir("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); cl_git_pass(git_path_lstat("r/mode2", &st)); check_mode(0755, st.st_mode); @@ -155,7 +194,7 @@ void test_core_mkdir__chmods(void) cl_git_pass(git_path_lstat("r/mode2/is2/important2", &st)); check_mode(0777, st.st_mode); - cl_git_pass(git_futils_mkdir("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode3", &st)); check_mode(0777, st.st_mode); @@ -166,7 +205,7 @@ void test_core_mkdir__chmods(void) /* test that we chmod existing dir */ - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -177,7 +216,7 @@ void test_core_mkdir__chmods(void) /* test that we chmod even existing dirs if CHMOD_PATH is set */ - cl_git_pass(git_futils_mkdir("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode2", &st)); check_mode(0777, st.st_mode); @@ -187,6 +226,40 @@ void test_core_mkdir__chmods(void) check_mode(0777, st.st_mode); } +void test_core_mkdir__keeps_parent_symlinks(void) +{ +#ifndef GIT_WIN32 + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_path_isdir("d0")); + + cl_must_pass(symlink("d0", "d1")); + cl_assert(git_path_islink("d1")); + + cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_path_islink("d1")); + cl_assert(git_path_isdir("d1/foo/bar")); + cl_assert(git_path_isdir("d0/foo/bar")); + + cl_must_pass(symlink("d0", "d2")); + cl_assert(git_path_islink("d2")); + + git_buf_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); + + cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_path_islink("d2")); + cl_assert(git_path_isdir("d2/other/dir")); + cl_assert(git_path_isdir("d0/other/dir")); + + git_buf_free(&path); +#endif +} + void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) { struct stat st; @@ -200,8 +273,8 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) *old = p_umask(022); cl_set_cleanup(cleanup_chmod_root, old); - cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0)); - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -210,10 +283,9 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) check_mode(0111, st.st_mode); cl_git_pass( - git_futils_mkdir("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH)); + git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode/is/okay/inside", &st)); check_mode(0755, st.st_mode); cl_must_pass(p_chmod("r/mode", 0777)); } - diff --git a/tests/core/opts.c b/tests/core/opts.c index 3173c648b..72408cbe8 100644 --- a/tests/core/opts.c +++ b/tests/core/opts.c @@ -17,3 +17,9 @@ void test_core_opts__readwrite(void) cl_assert(new_val == old_val); } + +void test_core_opts__invalid_option(void) +{ + cl_git_fail(git_libgit2_opts(-1, "foobar")); +} + diff --git a/tests/core/pool.c b/tests/core/pool.c index a7ec8801b..b07da0abd 100644 --- a/tests/core/pool.c +++ b/tests/core/pool.c @@ -8,7 +8,7 @@ void test_core_pool__0(void) git_pool p; void *ptr; - cl_git_pass(git_pool_init(&p, 1, 4000)); + git_pool_init(&p, 1); for (i = 1; i < 10000; i *= 2) { ptr = git_pool_malloc(&p, i); @@ -17,13 +17,6 @@ void test_core_pool__0(void) cl_assert(!git_pool__ptr_in_pool(&p, &i)); } - /* 1+2+4+8+16+32+64+128+256+512+1024 -> original block */ - /* 2048 -> 1 block */ - /* 4096 -> 1 block */ - /* 8192 -> 1 block */ - - cl_assert(git_pool__open_pages(&p) + git_pool__full_pages(&p) == 4); - git_pool_clear(&p); } @@ -32,26 +25,28 @@ void test_core_pool__1(void) int i; git_pool p; - cl_git_pass(git_pool_init(&p, 1, 4000)); + git_pool_init(&p, 1); + p.page_size = 4000; for (i = 2010; i > 0; i--) cl_assert(git_pool_malloc(&p, i) != NULL); +#ifndef GIT_DEBUG_POOL /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(1, git_pool__open_pages(&p)); - cl_assert_equal_i(507, git_pool__full_pages(&p)); - + cl_assert_equal_i(591, git_pool__open_pages(&p)); +#endif git_pool_clear(&p); - cl_git_pass(git_pool_init(&p, 1, 4120)); + git_pool_init(&p, 1); + p.page_size = 4120; for (i = 2010; i > 0; i--) cl_assert(git_pool_malloc(&p, i) != NULL); +#ifndef GIT_DEBUG_POOL /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(1, git_pool__open_pages(&p)); - cl_assert_equal_i(492, git_pool__full_pages(&p)); - + cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p)); +#endif git_pool_clear(&p); } @@ -66,7 +61,8 @@ void test_core_pool__2(void) memset(oid_hex, '0', sizeof(oid_hex)); - cl_git_pass(git_pool_init(&p, sizeof(git_oid), 100)); + git_pool_init(&p, sizeof(git_oid)); + p.page_size = 4000; for (i = 1000; i < 10000; i++) { oid = git_pool_malloc(&p, 1); @@ -77,60 +73,10 @@ void test_core_pool__2(void) cl_git_pass(git_oid_fromstr(oid, oid_hex)); } +#ifndef GIT_DEBUG_POOL /* with fixed page size, allocation must end up with these values */ - cl_assert(git_pool__open_pages(&p) == 0); - cl_assert(git_pool__full_pages(&p) == 90); - - git_pool_clear(&p); -} - -void test_core_pool__free_list(void) -{ - int i; - git_pool p; - void *ptr, *ptrs[50]; - - cl_git_pass(git_pool_init(&p, 100, 100)); - - for (i = 0; i < 10; ++i) { - ptr = git_pool_malloc(&p, 1); - cl_assert(ptr != NULL); - } - cl_assert_equal_i(10, (int)p.items); - - for (i = 0; i < 50; ++i) { - ptrs[i] = git_pool_malloc(&p, 1); - cl_assert(ptrs[i] != NULL); - } - cl_assert_equal_i(60, (int)p.items); - - git_pool_free(&p, ptr); - cl_assert_equal_i(60, (int)p.items); - - git_pool_free_array(&p, 50, ptrs); - cl_assert_equal_i(60, (int)p.items); - - for (i = 0; i < 50; ++i) { - ptrs[i] = git_pool_malloc(&p, 1); - cl_assert(ptrs[i] != NULL); - } - cl_assert_equal_i(60, (int)p.items); - - for (i = 0; i < 111; ++i) { - ptr = git_pool_malloc(&p, 1); - cl_assert(ptr != NULL); - } - cl_assert_equal_i(170, (int)p.items); - - git_pool_free_array(&p, 50, ptrs); - cl_assert_equal_i(170, (int)p.items); - - for (i = 0; i < 50; ++i) { - ptrs[i] = git_pool_malloc(&p, 1); - cl_assert(ptrs[i] != NULL); - } - cl_assert_equal_i(170, (int)p.items); - + cl_assert_equal_i(sizeof(void *) == 8 ? 55 : 45, git_pool__open_pages(&p)); +#endif git_pool_clear(&p); } @@ -138,7 +84,7 @@ void test_core_pool__strndup_limit(void) { git_pool p; - cl_git_pass(git_pool_init(&p, 1, 100)); + git_pool_init(&p, 1); /* ensure 64 bit doesn't overflow */ cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL); git_pool_clear(&p); diff --git a/tests/core/posix.c b/tests/core/posix.c index 5a9e24899..34a67bf47 100644 --- a/tests/core/posix.c +++ b/tests/core/posix.c @@ -100,7 +100,7 @@ void test_core_posix__inet_pton(void) void test_core_posix__utimes(void) { - struct timeval times[2]; + struct p_timeval times[2]; struct stat st; time_t curtime; int fd; diff --git a/tests/core/stat.c b/tests/core/stat.c index bd9b990e3..ef2e45a15 100644 --- a/tests/core/stat.c +++ b/tests/core/stat.c @@ -5,7 +5,7 @@ void test_core_stat__initialize(void) { - cl_git_pass(git_futils_mkdir("root/d1/d2", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); cl_git_mkfile("root/file", "whatever\n"); cl_git_mkfile("root/d1/file", "whatever\n"); } diff --git a/tests/core/stream.c b/tests/core/stream.c new file mode 100644 index 000000000..ace6a05da --- /dev/null +++ b/tests/core/stream.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "git2/sys/stream.h" +#include "tls_stream.h" +#include "stream.h" + +static git_stream test_stream; +static int ctor_called; + +static int test_ctor(git_stream **out, const char *host, const char *port) +{ + GIT_UNUSED(host); + GIT_UNUSED(port); + + ctor_called = 1; + *out = &test_stream; + + return 0; +} + +void test_core_stream__register_tls(void) +{ + git_stream *stream; + int error; + + ctor_called = 0; + cl_git_pass(git_stream_register_tls(test_ctor)); + cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); + + ctor_called = 0; + stream = NULL; + cl_git_pass(git_stream_register_tls(NULL)); + error = git_tls_stream_new(&stream, "localhost", "443"); + + /* We don't have arbitrary TLS stream support on Windows */ +#if GIT_WIN32 + cl_git_fail_with(-1, error); +#else + cl_git_pass(error); +#endif + + cl_assert_equal_i(0, ctor_called); + cl_assert(&test_stream != stream); + + git_stream_free(stream); +} diff --git a/tests/core/useragent.c b/tests/core/useragent.c new file mode 100644 index 000000000..6d06693a8 --- /dev/null +++ b/tests/core/useragent.c @@ -0,0 +1,11 @@ +#include "clar_libgit2.h" +#include "global.h" + +void test_core_useragent__get(void) +{ + const char *custom_name = "super duper git"; + + cl_assert_equal_p(NULL, git_libgit2__user_agent()); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); + cl_assert_equal_s(custom_name, git_libgit2__user_agent()); +} diff --git a/tests/diff/binary.c b/tests/diff/binary.c index 5298e9ebb..173a5994e 100644 --- a/tests/diff/binary.c +++ b/tests/diff/binary.c @@ -96,7 +96,8 @@ void test_diff_binary__add(void) "Kc${Nk-~s>u4FC%O\n" "\n" \ "literal 0\n" \ - "Hc$@u4FC%O\n"; + "Kc${Nk-~s>u4FC%O\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY; @@ -177,7 +179,8 @@ void test_diff_binary__delete(void) "Hc$@u4FC%O\n"; + "Kc${Nk-~s>u4FC%O\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -208,7 +211,8 @@ void test_diff_binary__delta(void) "delta 198\n" \ "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ - "JfH567LIF3FM2!Fd\n"; + "JfH567LIF3FM2!Fd\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -249,7 +253,8 @@ void test_diff_binary__delta_append(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -314,7 +319,8 @@ void test_diff_binary__index_to_workdir(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -379,7 +385,8 @@ void test_diff_binary__print_patch_from_diff(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; diff --git a/tests/diff/drivers.c b/tests/diff/drivers.c index e3a0014db..42af38a9a 100644 --- a/tests/diff/drivers.c +++ b/tests/diff/drivers.c @@ -250,3 +250,29 @@ void test_diff_drivers__builtins(void) git_buf_free(&expected); git_vector_free(&files); } + +void test_diff_drivers__invalid_pattern(void) +{ + git_config *cfg; + git_index *idx; + git_diff *diff; + git_patch *patch; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("userdiff"); + cl_git_mkfile("userdiff/.gitattributes", "*.storyboard diff=storyboard\n"); + + cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.storyboard.xfuncname", "")); + + cl_git_mkfile("userdiff/dummy.storyboard", ""); + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "dummy.storyboard")); + cl_git_mkfile("userdiff/dummy.storyboard", "some content\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + + git_patch_free(patch); + git_diff_free(diff); +} diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c index 18ad99bd5..e55afe958 100644 --- a/tests/diff/format_email.c +++ b/tests/diff/format_email.c @@ -97,6 +97,47 @@ void test_diff_format_email__simple(void) email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); } +void test_diff_format_email__with_message(void) +{ + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ + "From: Patrick Steinhardt \n" \ + "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ + "Subject: [PATCH] Modify content with message\n" \ + "\n" \ + "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly.\n" \ + "---\n" \ + " file3.txt | 1 +\n" \ + " 1 file changed, 1 insertion(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "index 9a2d780..7309653 100644\n" \ + "--- a/file3.txt\n" \ + "+++ b/file3.txt\n" \ + "@@ -3,3 +3,4 @@ file3!\n" \ + " file3\n" \ + " file3\n" \ + " file3\n" \ + "+file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + opts.body = "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly."; + + assert_email_match( + email, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270", &opts); +} + + void test_diff_format_email__multiple(void) { git_oid oid; diff --git a/tests/diff/index.c b/tests/diff/index.c index f702568bf..0293b7821 100644 --- a/tests/diff/index.c +++ b/tests/diff/index.c @@ -185,9 +185,9 @@ static void do_conflicted_diff(diff_expects *exp, unsigned long flags) ancestor.path = ours.path = theirs.path = "staged_changes"; ancestor.mode = ours.mode = theirs.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&ancestor.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - git_oid_fromstr(&ours.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - git_oid_fromstr(&theirs.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, &opts)); @@ -255,7 +255,7 @@ void test_diff_index__not_in_head_conflicted(void) theirs.path = "file_not_in_head"; theirs.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&theirs.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); cl_git_pass(git_index_conflict_add(index, NULL, NULL, &theirs)); cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, NULL)); @@ -268,3 +268,35 @@ void test_diff_index__not_in_head_conflicted(void) git_index_free(index); git_tree_free(a); } + +void test_diff_index__to_index(void) +{ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_tree *old_tree; + git_index *old_index; + git_index *new_index; + git_diff *diff; + diff_expects exp; + + cl_git_pass(git_index_new(&old_index)); + old_tree = resolve_commit_oid_to_tree(g_repo, a_commit); + cl_git_pass(git_index_read_tree(old_index, old_tree)); + + cl_git_pass(git_repository_index(&new_index, g_repo)); + + cl_git_pass(git_diff_index_to_index(&diff, g_repo, old_index, new_index, NULL)); + + memset(&exp, 0, sizeof(diff_expects)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_CONFLICTED]); + + git_diff_free(diff); + git_index_free(new_index); + git_index_free(old_index); + git_tree_free(old_tree); +} diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c index 6011c6a9b..8417e8ed4 100644 --- a/tests/diff/iterator.c +++ b/tests/diff/iterator.c @@ -30,13 +30,17 @@ static void tree_iterator_test( { git_tree *t; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; int error, count = 0, count_post_reset = 0; git_repository *repo = cl_git_sandbox_init(sandbox); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + cl_assert(t = resolve_commit_oid_to_tree(repo, treeish)); - cl_git_pass(git_iterator_for_tree( - &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, start, end)); + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); /* test loop */ while (!(error = git_iterator_advance(&entry, i))) { @@ -297,6 +301,7 @@ void test_diff_iterator__tree_special_functions(void) { git_tree *t; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; git_repository *repo = cl_git_sandbox_init("attr"); int error, cases = 0; @@ -306,8 +311,9 @@ void test_diff_iterator__tree_special_functions(void) repo, "24fa9a9fc4e202313e24b648087495441dab432b"); cl_assert(t != NULL); - cl_git_pass(git_iterator_for_tree( - &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); while (!(error = git_iterator_advance(&entry, i))) { cl_assert(entry); @@ -365,11 +371,16 @@ static void index_iterator_test( const git_index_entry *entry; int error, count = 0, caps; git_repository *repo = cl_git_sandbox_init(sandbox); + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; cl_git_pass(git_repository_index(&index, repo)); caps = git_index_caps(index); - cl_git_pass(git_iterator_for_index(&i, index, flags, start, end)); + iter_opts.flags = flags; + iter_opts.start = start; + iter_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, repo, index, &iter_opts)); while (!(error = git_iterator_advance(&entry, i))) { cl_assert(entry); @@ -581,12 +592,16 @@ static void workdir_iterator_test( const char *an_ignored_name) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; int error, count = 0, count_all = 0, count_all_post_reset = 0; git_repository *repo = cl_git_sandbox_init(sandbox); - cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, start, end)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_workdir(&i, repo, NULL, NULL, &i_opts)); error = git_iterator_current(&entry, i); cl_assert((error == 0 && entry != NULL) || @@ -765,6 +780,7 @@ void test_diff_iterator__workdir_builtin_ignores(void) { git_repository *repo = cl_git_sandbox_init("attr"); git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; int idx; static struct { @@ -796,8 +812,12 @@ void test_diff_iterator__workdir_builtin_ignores(void) cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); cl_git_mkfile("attr/sub/.git", "whatever"); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "dir"; + i_opts.end = "sub/sub/file"; + cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, "dir", "sub/sub/file")); + &i, repo, NULL, NULL, &i_opts)); cl_git_pass(git_iterator_current(&entry, i)); for (idx = 0; entry != NULL; ++idx) { @@ -827,12 +847,17 @@ static void check_wd_first_through_third_range( git_repository *repo, const char *start, const char *end) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; int error, idx; static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, GIT_ITERATOR_IGNORE_CASE, start, end)); + &i, repo, NULL, NULL, &i_opts)); cl_git_pass(git_iterator_current(&entry, i)); for (idx = 0; entry != NULL; ++idx) { @@ -877,14 +902,16 @@ static void check_tree_range( { git_tree *head; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; int error, count; + i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_iterator_for_tree( - &i, head, - ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE, - start, end)); + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) /* count em up */; @@ -931,6 +958,7 @@ static void check_index_range( { git_index *index; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; int error, count, caps; bool is_ignoring_case; @@ -942,7 +970,11 @@ static void check_index_range( if (ignore_case != is_ignoring_case) cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE)); - cl_git_pass(git_iterator_for_index(&i, index, 0, start, end)); + i_opts.flags = 0; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts)); cl_assert(git_iterator_ignore_case(i) == ignore_case); diff --git a/tests/diff/notify.c b/tests/diff/notify.c index 6ef4af573..653512795 100644 --- a/tests/diff/notify.c +++ b/tests/diff/notify.c @@ -55,7 +55,7 @@ static void test_notify( opts.pathspec.strings = searched_pathspecs; opts.pathspec.count = pathspecs_count; - opts.notify_payload = expected_matched_pathspecs; + opts.payload = expected_matched_pathspecs; memset(&exp, 0, sizeof(exp)); cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); @@ -228,3 +228,31 @@ void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void) git_diff_free(diff); } + +static int progress_abort_diff( + const git_diff *diff_so_far, + const char *old_path, + const char *new_path, + void *payload) +{ + GIT_UNUSED(diff_so_far); + GIT_UNUSED(old_path); + GIT_UNUSED(new_path); + GIT_UNUSED(payload); + + return -42; +} + +void test_diff_notify__progress_cb_can_abort_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + + g_repo = cl_git_sandbox_init("status"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.progress_cb = progress_abort_diff; + + cl_git_fail_with( + git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); +} diff --git a/tests/diff/tree.c b/tests/diff/tree.c index 2bc9e6a55..e4b2a8bbe 100644 --- a/tests/diff/tree.c +++ b/tests/diff/tree.c @@ -90,7 +90,7 @@ void test_diff_tree__0(void) #define DIFF_OPTS(FLAGS, CTXT) \ {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_UNSPECIFIED, \ - {NULL,0}, NULL, NULL, (CTXT), 1} + {NULL,0}, NULL, NULL, NULL, (CTXT), 1} void test_diff_tree__options(void) { diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 8a23f53ae..e1bbce8fb 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -85,9 +85,11 @@ void test_diff_workdir__to_index_with_conflicts(void) /* Adding an entry that represents a rename gets two files in conflict */ our_entry.path = "subdir/modified_file"; our_entry.mode = 0100644; + git_oid_fromstr(&our_entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); their_entry.path = "subdir/rename_conflict"; their_entry.mode = 0100644; + git_oid_fromstr(&their_entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); cl_git_pass(git_repository_index(&index, g_repo)); cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry)); @@ -444,6 +446,216 @@ void test_diff_workdir__to_index_with_pathspec(void) git_diff_free(diff); } +void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 0; + + /* ensure that an empty pathspec list is ignored */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that a single NULL pathspec is filtered out (like when using + * fnmatch filtering) + */ + + opts.pathspec.count = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "modified_file"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified */ + pathspec = "subdir"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified with a trailing slash */ + pathspec = "subdir/"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching is completely disabled */ + pathspec = "subdir/*"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that the prefix matching isn't completely braindead */ + pathspec = "subdi"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching isn't working at all */ + pathspec = "*_deleted"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); +} + void test_diff_workdir__filemode_changes(void) { git_diff *diff = NULL; @@ -1545,7 +1757,7 @@ void test_diff_workdir__with_stale_index(void) static int touch_file(void *payload, git_buf *path) { struct stat st; - struct timeval times[2]; + struct p_timeval times[2]; GIT_UNUSED(payload); if (git_path_isdir(path->ptr)) @@ -1765,9 +1977,9 @@ void test_diff_workdir__to_index_conflicted(void) { ancestor.path = ours.path = theirs.path = "_file"; ancestor.mode = ours.mode = theirs.mode = 0100644; - git_oid_fromstr(&ancestor.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - git_oid_fromstr(&ours.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - git_oid_fromstr(&theirs.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); cl_git_pass(git_diff_tree_to_index(&diff1, g_repo, a, index, NULL)); @@ -1796,7 +2008,7 @@ void test_diff_workdir__only_writes_index_when_necessary(void) git_oid initial, first, second; git_buf path = GIT_BUF_INIT; struct stat st; - struct timeval times[2]; + struct p_timeval times[2]; opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_UPDATE_INDEX; @@ -1844,3 +2056,107 @@ void test_diff_workdir__only_writes_index_when_necessary(void) git_index_free(index); } +void test_diff_workdir__to_index_pathlist(void) +{ + git_index *index; + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector pathlist = GIT_VECTOR_INIT; + + git_vector_insert(&pathlist, "foobar/asdf"); + git_vector_insert(&pathlist, "subdir/asdf"); + git_vector_insert(&pathlist, "ignored/asdf"); + + g_repo = cl_git_sandbox_init("status"); + + cl_git_mkfile("status/.gitignore", ".gitignore\n" "ignored/\n"); + + cl_must_pass(p_mkdir("status/foobar", 0777)); + cl_git_mkfile("status/foobar/one", "one\n"); + + cl_must_pass(p_mkdir("status/ignored", 0777)); + cl_git_mkfile("status/ignored/one", "one\n"); + cl_git_mkfile("status/ignored/two", "two\n"); + cl_git_mkfile("status/ignored/three", "three\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED; + opts.pathspec.strings = (char **)pathlist.contents; + opts.pathspec.count = pathlist.length; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + git_index_free(index); + git_vector_free(&pathlist); +} + +void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) +{ + git_tree *tree; + git_diff *diff; + diff_expects exp = {0}; + const git_diff_delta *delta; + const char *commit = "7fccd7"; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector pathlist = GIT_VECTOR_INIT; + int symlinks; + + g_repo = cl_git_sandbox_init("unsymlinked.git"); + + cl_git_pass(git_repository__cvar(&symlinks, g_repo, GIT_CVAR_SYMLINKS)); + + if (symlinks) + cl_skip(); + + cl_git_pass(git_vector_insert(&pathlist, "include/Nu/Nu.h")); + + opts.pathspec.strings = (char **)pathlist.contents; + opts.pathspec.count = pathlist.length; + + cl_must_pass(p_mkdir("symlink", 0777)); + cl_git_pass(git_repository_set_workdir(g_repo, "symlink", false)); + + cl_assert((tree = resolve_commit_oid_to_tree(g_repo, commit)) != NULL); + + /* first, do the diff with the original contents */ + + cl_git_pass(git_futils_mkpath2file("symlink/include/Nu/Nu.h", 0755)); + cl_git_mkfile("symlink/include/Nu/Nu.h", "../../objc/Nu.h"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + /* now update the contents and expect a difference, but that the file + * mode has persisted as a symbolic link. + */ + + cl_git_rewritefile("symlink/include/Nu/Nu.h", "awesome content\n"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_i(GIT_FILEMODE_LINK, delta->old_file.mode); + cl_assert_equal_i(GIT_FILEMODE_LINK, delta->new_file.mode); + + git_diff_free(diff); + + cl_git_pass(git_futils_rmdir_r("symlink", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_tree_free(tree); + git_vector_free(&pathlist); +} diff --git a/tests/filter/custom.c b/tests/filter/custom.c index 493d26c80..fd1cd271c 100644 --- a/tests/filter/custom.c +++ b/tests/filter/custom.c @@ -5,6 +5,7 @@ #include "buf_text.h" #include "git2/sys/filter.h" #include "git2/sys/repository.h" +#include "custom_helpers.h" /* going TO_WORKDIR, filters are executed low to high * going TO_ODB, filters are executed high to low @@ -12,8 +13,6 @@ #define BITFLIP_FILTER_PRIORITY -1 #define REVERSE_FILTER_PRIORITY -2 -#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff) - #ifdef GIT_WIN32 # define NEWLINE "\r\n" #else @@ -27,6 +26,8 @@ static char workdir_data[] = "trivially" NEWLINE "scrambled." NEWLINE; +#define REVERSED_DATA_LEN 51 + /* Represents the data above scrambled (bits flipped) after \r\n -> \n * conversion, then bytewise reversed */ @@ -63,107 +64,6 @@ void test_filter_custom__cleanup(void) g_repo = NULL; } -static int bitflip_filter_apply( - git_filter *self, - void **payload, - git_buf *to, - const git_buf *from, - const git_filter_source *source) -{ - const unsigned char *src = (const unsigned char *)from->ptr; - unsigned char *dst; - size_t i; - - GIT_UNUSED(self); GIT_UNUSED(payload); - - /* verify that attribute path match worked as expected */ - cl_assert_equal_i( - 0, git__strncmp("hero", git_filter_source_path(source), 4)); - - if (!from->size) - return 0; - - cl_git_pass(git_buf_grow(to, from->size)); - - dst = (unsigned char *)to->ptr; - - for (i = 0; i < from->size; i++) - dst[i] = VERY_SECURE_ENCRYPTION(src[i]); - - to->size = from->size; - - return 0; -} - -static void bitflip_filter_free(git_filter *f) -{ - git__free(f); -} - -static git_filter *create_bitflip_filter(void) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = "+bitflip"; - filter->shutdown = bitflip_filter_free; - filter->apply = bitflip_filter_apply; - - return filter; -} - - -static int reverse_filter_apply( - git_filter *self, - void **payload, - git_buf *to, - const git_buf *from, - const git_filter_source *source) -{ - const unsigned char *src = (const unsigned char *)from->ptr; - const unsigned char *end = src + from->size; - unsigned char *dst; - - GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source); - - /* verify that attribute path match worked as expected */ - cl_assert_equal_i( - 0, git__strncmp("hero", git_filter_source_path(source), 4)); - - if (!from->size) - return 0; - - cl_git_pass(git_buf_grow(to, from->size)); - - dst = (unsigned char *)to->ptr + from->size - 1; - - while (src < end) - *dst-- = *src++; - - to->size = from->size; - - return 0; -} - -static void reverse_filter_free(git_filter *f) -{ - git__free(f); -} - -static git_filter *create_reverse_filter(const char *attrs) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = attrs; - filter->shutdown = reverse_filter_free; - filter->apply = reverse_filter_apply; - - return filter; -} - static void register_custom_filters(void) { static int filters_registered = 0; @@ -186,7 +86,6 @@ static void register_custom_filters(void) } } - void test_filter_custom__to_odb(void) { git_filter_list *fl; diff --git a/tests/filter/custom_helpers.c b/tests/filter/custom_helpers.c new file mode 100644 index 000000000..2c80212be --- /dev/null +++ b/tests/filter/custom_helpers.c @@ -0,0 +1,108 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "filter.h" +#include "buf_text.h" +#include "git2/sys/filter.h" + +#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff) + +int bitflip_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + unsigned char *dst; + size_t i; + + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_buf_grow(to, from->size)); + + dst = (unsigned char *)to->ptr; + + for (i = 0; i < from->size; i++) + dst[i] = VERY_SECURE_ENCRYPTION(src[i]); + + to->size = from->size; + + return 0; +} + +static void bitflip_filter_free(git_filter *f) +{ + git__free(f); +} + +git_filter *create_bitflip_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "+bitflip"; + filter->shutdown = bitflip_filter_free; + filter->apply = bitflip_filter_apply; + + return filter; +} + + +int reverse_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + const unsigned char *end = src + from->size; + unsigned char *dst; + + GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_buf_grow(to, from->size)); + + dst = (unsigned char *)to->ptr + from->size - 1; + + while (src < end) + *dst-- = *src++; + + to->size = from->size; + + return 0; +} + +static void reverse_filter_free(git_filter *f) +{ + git__free(f); +} + +git_filter *create_reverse_filter(const char *attrs) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = attrs; + filter->shutdown = reverse_filter_free; + filter->apply = reverse_filter_apply; + + return filter; +} diff --git a/tests/filter/custom_helpers.h b/tests/filter/custom_helpers.h new file mode 100644 index 000000000..13cfb23ae --- /dev/null +++ b/tests/filter/custom_helpers.h @@ -0,0 +1,18 @@ +#include "git2/sys/filter.h" + +extern git_filter *create_bitflip_filter(void); +extern git_filter *create_reverse_filter(const char *attr); + +extern int bitflip_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source); + +extern int reverse_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source); diff --git a/tests/filter/stream.c b/tests/filter/stream.c index 6bf540ce7..30f5e5027 100644 --- a/tests/filter/stream.c +++ b/tests/filter/stream.c @@ -209,7 +209,7 @@ void test_filter_stream__smallfile(void) /* optionally write a 500 MB file through the compression stream */ void test_filter_stream__bigfile(void) { - if (!cl_getenv("GITTEST_INVASIVE_FS_SIZE")) + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) cl_skip(); test_stream(51200); diff --git a/tests/filter/wildcard.c b/tests/filter/wildcard.c new file mode 100644 index 000000000..999b33653 --- /dev/null +++ b/tests/filter/wildcard.c @@ -0,0 +1,184 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" +#include "buf_text.h" +#include "git2/sys/filter.h" +#include "git2/sys/repository.h" +#include "custom_helpers.h" + +static git_repository *g_repo = NULL; + +static git_filter *create_wildcard_filter(void); + +#define DATA_LEN 32 + +static unsigned char input[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +}; + +static unsigned char reversed[] = { + 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, +}; + +static unsigned char flipped[] = { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, +}; + +void test_filter_wildcard__initialize(void) +{ + cl_git_pass(git_filter_register( + "wildcard", create_wildcard_filter(), GIT_FILTER_DRIVER_PRIORITY)); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_rewritefile( + "empty_standard_repo/.gitattributes", + "* binary\n" + "hero-flip-* filter=wcflip\n" + "hero-reverse-* filter=wcreverse\n" + "none-* filter=unregistered\n"); +} + +void test_filter_wildcard__cleanup(void) +{ + cl_git_pass(git_filter_unregister("wildcard")); + + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static int wildcard_filter_check( + git_filter *self, + void **payload, + const git_filter_source *src, + const char **attr_values) +{ + GIT_UNUSED(self); + GIT_UNUSED(src); + + if (strcmp(attr_values[0], "wcflip") == 0 || + strcmp(attr_values[0], "wcreverse") == 0) { + *payload = git__strdup(attr_values[0]); + GITERR_CHECK_ALLOC(*payload); + return 0; + } + + return GIT_PASSTHROUGH; +} + +static int wildcard_filter_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *source) +{ + const char *filtername = *payload; + + if (filtername && strcmp(filtername, "wcflip") == 0) + return bitflip_filter_apply(self, payload, to, from, source); + else if (filtername && strcmp(filtername, "wcreverse") == 0) + return reverse_filter_apply(self, payload, to, from, source); + + cl_fail("Unexpected attribute"); + return GIT_PASSTHROUGH; +} + +static void wildcard_filter_cleanup(git_filter *self, void *payload) +{ + GIT_UNUSED(self); + git__free(payload); +} + +static void wildcard_filter_free(git_filter *f) +{ + git__free(f); +} + +static git_filter *create_wildcard_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "filter=*"; + filter->check = wildcard_filter_check; + filter->apply = wildcard_filter_apply; + filter->cleanup = wildcard_filter_cleanup; + filter->shutdown = wildcard_filter_free; + + return filter; +} + +void test_filter_wildcard__reverse(void) +{ + git_filter_list *fl; + git_buf in = GIT_BUF_INIT, out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero-reverse-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_buf_put(&in, (char *)input, DATA_LEN)); + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(reversed, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); + git_buf_free(&in); +} + +void test_filter_wildcard__flip(void) +{ + git_filter_list *fl; + git_buf in = GIT_BUF_INIT, out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero-flip-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_buf_put(&in, (char *)input, DATA_LEN)); + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(flipped, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); + git_buf_free(&in); +} + +void test_filter_wildcard__none(void) +{ + git_filter_list *fl; + git_buf in = GIT_BUF_INIT, out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "none-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_buf_put(&in, (char *)input, DATA_LEN)); + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(input, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); + git_buf_free(&in); +} diff --git a/tests/index/add.c b/tests/index/add.c new file mode 100644 index 000000000..f101ea266 --- /dev/null +++ b/tests/index/add.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo = NULL; +static git_index *g_index = NULL; + +static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; +static const char *valid_tree_id = "181037049a54a1eb5fab404658a3a250b44335d7"; +static const char *valid_commit_id = "763d71aadf09a7951596c9746c024e7eece7c7af"; +static const char *invalid_id = "1234567890123456789012345678901234567890"; + +void test_index_add__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_index(&g_index, g_repo)); +} + +void test_index_add__cleanup(void) +{ + git_index_free(g_index); + cl_git_sandbox_cleanup(); + g_repo = NULL; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); +} + +static void test_add_entry( + bool should_succeed, const char *idstr, git_filemode_t mode) +{ + git_index_entry entry = {{0}}; + + cl_git_pass(git_oid_fromstr(&entry.id, idstr)); + + entry.path = mode == GIT_FILEMODE_TREE ? "test_folder" : "test_file"; + entry.mode = mode; + + if (should_succeed) + cl_git_pass(git_index_add(g_index, &entry)); + else + cl_git_fail(git_index_add(g_index, &entry)); +} + +void test_index_add__invalid_entries_succeeds_by_default(void) +{ + /* + * Ensure that there is validation on object ids by default + */ + + /* ensure that we can add some actually good entries */ + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); + + /* test that we fail to add some invalid (missing) blobs and trees */ + test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB); + test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(false, invalid_id, GIT_FILEMODE_LINK); + + /* test that we validate the types of objects */ + test_add_entry(false, valid_commit_id, GIT_FILEMODE_BLOB); + test_add_entry(false, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(false, valid_commit_id, GIT_FILEMODE_LINK); + + /* + * Ensure that there we can disable validation + */ + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + + /* ensure that we can add some actually good entries */ + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); + + /* test that we can now add some invalid (missing) blobs and trees */ + test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB); + test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, invalid_id, GIT_FILEMODE_LINK); + + /* test that we do not validate the types of objects */ + test_add_entry(true, valid_commit_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_commit_id, GIT_FILEMODE_LINK); +} + diff --git a/tests/index/addall.c b/tests/index/addall.c index 9ddb27f95..7b7a178d1 100644 --- a/tests/index/addall.c +++ b/tests/index/addall.c @@ -307,6 +307,41 @@ void test_index_addall__files_in_folders(void) git_index_free(index); } +void test_index_addall__hidden_files(void) +{ + git_index *index; + + GIT_UNUSED(index); + +#ifdef GIT_WIN32 + addall_create_test_repo(true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true)); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); + + git_index_free(index); +#endif +} + static int addall_match_prefix( const char *path, const char *matched_pathspec, void *payload) { diff --git a/tests/index/bypath.c b/tests/index/bypath.c index 9706a8833..34a7412a8 100644 --- a/tests/index/bypath.c +++ b/tests/index/bypath.c @@ -46,3 +46,317 @@ void test_index_bypath__add_submodule_unregistered(void) cl_assert_equal_s(sm_head, git_oid_tostr_s(&entry->id)); cl_assert_equal_s(sm_name, entry->path); } + +void test_index_bypath__add_hidden(void) +{ + const git_index_entry *entry; + bool hidden; + + GIT_UNUSED(entry); + GIT_UNUSED(hidden); + +#ifdef GIT_WIN32 + cl_git_mkfile("submod2/hidden_file", "you can't see me"); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(!hidden); + + cl_git_pass(git_win32__set_hidden("submod2/hidden_file", true)); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(hidden); + + cl_git_pass(git_index_add_bypath(g_idx, "hidden_file")); + + cl_assert(entry = git_index_get_bypath(g_idx, "hidden_file", 0)); + cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode); +#endif +} + +void test_index_bypath__add_keeps_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/file1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/file1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file1.txt", "Updated!"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/FILE1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/FILE1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("just_a_dir/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_2(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + /* note that `git_index_add` does no checking to canonical directories */ + dummy.path = "Just_a_dir/file0.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/fileA.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_A_DIR/fileC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_dir/fileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_a_DIR/fileE.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JusT_A_DIR/FILE4.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("JUST_A_DIR/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_3(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + dummy.path = "just_a_dir/filea.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_DIR/FILEC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_a_DIR/FileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/JuSt_A_DiR/fILEE.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/fILEE.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/fILEE.txt", 0)); + cl_assert_equal_s("just_a_dir/fILEE.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_4(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + dummy.path = "just_a_dir/a/b/c/d/e/file1.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/a/B/C/D/E/file2.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_must_pass(p_mkdir("submod2/just_a_dir/a", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y/x", 0777)); + + cl_git_mkfile("submod2/just_a_dir/a/b/z/y/x/FOO.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt", 0)); + cl_assert_equal_s("just_a_dir/a/b/Z/y/X/foo.txt", entry->path); +} + +void test_index_bypath__add_honors_mode(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_add(g_idx, &new_entry)); + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_conflict_mode(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int stage = 0; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); + + for (stage = 1; stage <= 3; stage++) { + new_entry.flags = stage << GIT_IDXENTRY_STAGESHIFT; + cl_git_pass(git_index_add(g_idx, &new_entry)); + } + + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_conflict_case(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int stage = 0; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); + + for (stage = 1; stage <= 3; stage++) { + new_entry.flags = stage << GIT_IDXENTRY_STAGESHIFT; + cl_git_pass(git_index_add(g_idx, &new_entry)); + } + + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_symlink(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int symlinks; + + cl_git_pass(git_repository__cvar(&symlinks, g_repo, GIT_CVAR_SYMLINKS)); + + if (symlinks) + cl_skip(); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_LINK; + + cl_git_pass(git_index_add(g_idx, &new_entry)); + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still a (fake) symlink"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_LINK, entry->mode); +} diff --git a/tests/index/cache.c b/tests/index/cache.c index 3982bf183..56885aff7 100644 --- a/tests/index/cache.c +++ b/tests/index/cache.c @@ -111,7 +111,7 @@ void test_index_cache__read_tree_no_children(void) memset(&entry, 0x0, sizeof(git_index_entry)); entry.path = "new.txt"; entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + git_oid_fromstr(&entry.id, "d4bcc68acd4410bf836a39f20afb2c2ece09584e"); cl_git_pass(git_index_add(index, &entry)); cl_assert_equal_i(-1, index->tree->entry_count); @@ -191,7 +191,7 @@ void test_index_cache__read_tree_children(void) memset(&entry, 0x0, sizeof(git_index_entry)); entry.path = "top-level"; entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); cl_git_pass(git_index_add(index, &entry)); @@ -217,7 +217,7 @@ void test_index_cache__read_tree_children(void) /* override with a slightly different id, also dummy */ entry.path = "subdir/some-file"; - git_oid_fromstr(&entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb058"); + git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); cl_git_pass(git_index_add(index, &entry)); cl_assert_equal_i(-1, index->tree->entry_count); diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index b7a2456eb..d4004686f 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -16,11 +16,6 @@ static git_index *repo_index; #define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" #define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" -#define TEST_STAGED_OID "beefdadafeedabedcafedeedbabedeadbeaddeaf" -#define TEST_ANCESTOR_OID "f00ff00ff00ff00ff00ff00ff00ff00ff00ff00f" -#define TEST_OUR_OID "b44bb44bb44bb44bb44bb44bb44bb44bb44bb44b" -#define TEST_THEIR_OID "0123456789abcdef0123456789abcdef01234567" - // Fixture setup and teardown void test_index_conflicts__initialize(void) { @@ -49,17 +44,17 @@ void test_index_conflicts__add(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&our_entry, 2); - git_oid_fromstr(&our_entry.id, TEST_OUR_OID); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 2); - git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -80,17 +75,17 @@ void test_index_conflicts__add_fixes_incorrect_stage(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 3); - git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&our_entry, 1); - git_oid_fromstr(&our_entry.id, TEST_OUR_OID); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&their_entry, 2); - git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -105,36 +100,33 @@ void test_index_conflicts__add_fixes_incorrect_stage(void) void test_index_conflicts__add_removes_stage_zero(void) { - git_index_entry staged, ancestor_entry, our_entry, their_entry; + git_index_entry ancestor_entry, our_entry, their_entry; const git_index_entry *conflict_entry[3]; cl_assert(git_index_entrycount(repo_index) == 8); - memset(&staged, 0x0, sizeof(git_index_entry)); memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); memset(&our_entry, 0x0, sizeof(git_index_entry)); memset(&their_entry, 0x0, sizeof(git_index_entry)); - staged.path = "test-one.txt"; - staged.mode = 0100644; - git_oid_fromstr(&staged.id, TEST_STAGED_OID); - cl_git_pass(git_index_add(repo_index, &staged)); + cl_git_mkfile("./mergedrepo/test-one.txt", "new-file\n"); + cl_git_pass(git_index_add_bypath(repo_index, "test-one.txt")); cl_assert(git_index_entrycount(repo_index) == 9); ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 3); - git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&our_entry, 1); - git_oid_fromstr(&our_entry.id, TEST_OUR_OID); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&their_entry, 2); - git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -330,7 +322,7 @@ void test_index_conflicts__partial(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); cl_assert(git_index_entrycount(repo_index) == 9); @@ -342,3 +334,94 @@ void test_index_conflicts__partial(void) cl_assert(conflict_entry[1] == NULL); cl_assert(conflict_entry[2] == NULL); } + +void test_index_conflicts__case_matters(void) +{ + const git_index_entry *conflict_entry[3]; + git_oid oid; + const char *upper_case = "DIFFERS-IN-CASE.TXT"; + const char *mixed_case = "Differs-In-Case.txt"; + const char *correct_case; + bool ignorecase = cl_repo_get_bool(repo, "core.ignorecase"); + + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = upper_case; + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + our_entry.path = upper_case; + GIT_IDXENTRY_STAGE_SET(&our_entry, GIT_INDEX_STAGE_OURS); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + our_entry.mode = GIT_FILEMODE_BLOB; + + their_entry.path = upper_case; + GIT_IDXENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + their_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_conflict_add(repo_index, + &ancestor_entry, &our_entry, &their_entry)); + + ancestor_entry.path = mixed_case; + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_TWO_ANCESTOR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + our_entry.path = mixed_case; + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&our_entry.id, CONFLICTS_TWO_OUR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + their_entry.path = mixed_case; + GIT_IDXENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); + git_oid_fromstr(&their_entry.id, CONFLICTS_TWO_THEIR_OID); + their_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_conflict_add(repo_index, + &ancestor_entry, &our_entry, &their_entry)); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, upper_case)); + + /* + * We inserted with mixed case last, so on a case-insensitive + * fs we should get the mixed case. + */ + if (ignorecase) + correct_case = mixed_case; + else + correct_case = upper_case; + + cl_assert_equal_s(correct_case, conflict_entry[0]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_ANCESTOR_OID : CONFLICTS_ONE_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + cl_assert_equal_s(correct_case, conflict_entry[1]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_OUR_OID : CONFLICTS_ONE_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + cl_assert_equal_s(correct_case, conflict_entry[2]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_THEIR_OID : CONFLICTS_ONE_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, mixed_case)); + + cl_assert_equal_s(mixed_case, conflict_entry[0]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + cl_assert_equal_s(mixed_case, conflict_entry[1]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + cl_assert_equal_s(mixed_case, conflict_entry[2]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); +} diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index b3907996b..2efad5b33 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -236,11 +236,19 @@ void test_index_filemodes__invalid(void) { git_index *index; git_index_entry entry; + const git_index_entry *dummy; cl_git_pass(git_repository_index(&index, g_repo)); + /* add a dummy file so that we have a valid id */ + cl_git_mkfile("./filemodes/dummy-file.txt", "new-file\n"); + cl_git_pass(git_index_add_bypath(index, "dummy-file.txt")); + cl_assert((dummy = git_index_get_bypath(index, "dummy-file.txt", 0))); + + GIT_IDXENTRY_STAGE_SET(&entry, 0); entry.path = "foo"; entry.mode = GIT_OBJ_BLOB; + git_oid_cpy(&entry.id, &dummy->id); cl_git_fail(git_index_add(index, &entry)); entry.mode = GIT_FILEMODE_BLOB; diff --git a/tests/index/nsec.c b/tests/index/nsec.c new file mode 100644 index 000000000..244ab6362 --- /dev/null +++ b/tests/index/nsec.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/sys/index.h" +#include "git2/repository.h" +#include "../reset/reset_helpers.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "nsecs" + +// Fixture setup and teardown +void test_index_nsec__initialize(void) +{ + repo = cl_git_sandbox_init("nsecs"); + git_repository_index(&repo_index, repo); +} + +void test_index_nsec__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +static bool has_nsecs(void) +{ + const git_index_entry *entry; + size_t i; + bool has_nsecs = false; + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + entry = git_index_get_byindex(repo_index, i); + + if (entry->ctime.nanoseconds || entry->mtime.nanoseconds) { + has_nsecs = true; + break; + } + } + + return has_nsecs; +} + +void test_index_nsec__has_nanos(void) +{ + cl_assert_equal_b(true, has_nsecs()); +} + +void test_index_nsec__staging_maintains_other_nanos(void) +{ + const git_index_entry *entry; + + cl_git_rewritefile("nsecs/a.txt", "This is file A"); + cl_git_pass(git_index_add_bypath(repo_index, "a.txt")); + cl_git_pass(git_index_write(repo_index)); + + cl_git_pass(git_index_write(repo_index)); + + git_index_read(repo_index, 1); + cl_assert_equal_b(true, has_nsecs()); + + cl_assert((entry = git_index_get_bypath(repo_index, "a.txt", 0))); + + /* if we are writing nanoseconds to the index, expect them to be + * nonzero. if we are *not*, expect that we truncated the entry. + */ +#ifdef GIT_USE_NSEC + cl_assert(entry->ctime.nanoseconds != 0); + cl_assert(entry->mtime.nanoseconds != 0); +#else + cl_assert_equal_i(0, entry->ctime.nanoseconds); + cl_assert_equal_i(0, entry->mtime.nanoseconds); +#endif +} + +void test_index_nsec__status_doesnt_clear_nsecs(void) +{ + git_status_list *statuslist; + + cl_git_pass(git_status_list_new(&statuslist, repo, NULL)); + + git_index_read(repo_index, 1); + cl_assert_equal_b(true, has_nsecs()); + + git_status_list_free(statuslist); +} diff --git a/tests/index/racy.c b/tests/index/racy.c index 3b26aabf4..68aa46007 100644 --- a/tests/index/racy.c +++ b/tests/index/racy.c @@ -54,7 +54,7 @@ void test_index_racy__write_index_just_after_file(void) git_index *index; git_diff *diff; git_buf path = GIT_BUF_INIT; - struct timeval times[2]; + struct p_timeval times[2]; /* Make sure we do have a timestamp */ cl_git_pass(git_repository_index(&index, g_repo)); @@ -63,10 +63,10 @@ void test_index_racy__write_index_just_after_file(void) cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "A")); cl_git_mkfile(path.ptr, "A"); /* Force the file's timestamp to be a second after we wrote the index */ - times[0].tv_sec = index->stamp.mtime + 1; - times[0].tv_usec = 0; - times[1].tv_sec = index->stamp.mtime + 1; - times[1].tv_usec = 0; + times[0].tv_sec = index->stamp.mtime.tv_sec + 1; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; + times[1].tv_sec = index->stamp.mtime.tv_sec + 1; + times[1].tv_usec = index->stamp.mtime.tv_nsec / 1000; cl_git_pass(p_utimes(path.ptr, times)); /* @@ -79,13 +79,13 @@ void test_index_racy__write_index_just_after_file(void) cl_git_mkfile(path.ptr, "B"); /* - * Pretend this index' modification happend a second after the + * Pretend this index' modification happened a second after the * file update, and rewrite the file in that same second. */ - times[0].tv_sec = index->stamp.mtime + 2; - times[0].tv_usec = 0; - times[1].tv_sec = index->stamp.mtime + 2; - times[0].tv_usec = 0; + times[0].tv_sec = index->stamp.mtime.tv_sec + 2; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; + times[1].tv_sec = index->stamp.mtime.tv_sec + 2; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; cl_git_pass(p_utimes(git_index_path(index), times)); cl_git_pass(p_utimes(path.ptr, times)); @@ -100,13 +100,13 @@ void test_index_racy__write_index_just_after_file(void) git_index_free(index); } -void test_index_racy__empty_file_after_smudge(void) + +static void setup_race(void) { - git_index *index; - git_diff *diff; git_buf path = GIT_BUF_INIT; - int i, found_race = 0; + git_index *index; const git_index_entry *entry; + int i, found_race = 0; /* Make sure we do have a timestamp */ cl_git_pass(git_repository_index__weakptr(&index, g_repo)); @@ -131,17 +131,201 @@ void test_index_racy__empty_file_after_smudge(void) found_race = 1; break; } - } if (!found_race) cl_fail("failed to find race after 10 attempts"); + git_buf_free(&path); +} + +void test_index_racy__smudges_index_entry_on_save(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_race(); + + /* write the index, which will smudge anything that had the same timestamp + * as the index when the index was loaded. that way future loads of the + * index (with the new timestamp) will know that these files were not + * clean. + */ + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_assert(entry = git_index_get_bypath(index, "A", 0)); cl_assert_equal_i(0, entry->file_size); +} + +void test_index_racy__detects_diff_of_change_in_identical_timestamp(void) +{ + git_index *index; + git_diff *diff; + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + + setup_race(); cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); cl_assert_equal_i(1, git_diff_num_deltas(diff)); - git_buf_free(&path); git_diff_free(diff); } + +static void setup_uptodate_files(void) +{ + git_buf path = GIT_BUF_INIT; + git_index *index; + const git_index_entry *a_entry; + git_index_entry new_entry = {{0}}; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "A")); + cl_git_mkfile(path.ptr, "A"); + + /* Put 'A' into the index */ + cl_git_pass(git_index_add_bypath(index, "A")); + + cl_assert((a_entry = git_index_get_bypath(index, "A", 0))); + + /* Put 'B' into the index */ + new_entry.path = "B"; + new_entry.mode = GIT_FILEMODE_BLOB; + git_oid_cpy(&new_entry.id, &a_entry->id); + cl_git_pass(git_index_add(index, &new_entry)); + + /* Put 'C' into the index */ + new_entry.path = "C"; + new_entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add_frombuffer(index, &new_entry, "hello!\n", 7)); + + git_index_free(index); + git_buf_free(&path); +} + +void test_index_racy__adding_to_index_is_uptodate(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* ensure that they're all uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(GIT_IDXENTRY_UPTODATE, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(GIT_IDXENTRY_UPTODATE, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(GIT_IDXENTRY_UPTODATE, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_git_pass(git_index_write(index)); + + git_index_free(index); +} + +void test_index_racy__reading_clears_uptodate_bit(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); + + /* ensure that no files are uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + git_index_free(index); +} + +void test_index_racy__read_tree_clears_uptodate_bit(void) +{ + git_index *index; + git_tree *tree; + const git_index_entry *entry; + git_oid id; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write_tree_to(&id, index, g_repo)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_index_read_tree(index, tree)); + + /* ensure that no files are uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + git_tree_free(tree); + git_index_free(index); +} + +void test_index_racy__read_index_smudges(void) +{ + git_index *index, *newindex; + const git_index_entry *entry; + + /* if we are reading an index into our new index, ensure that any + * racy entries in the index that we're reading are smudged so that + * we don't propagate their timestamps without further investigation. + */ + setup_race(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_new(&newindex)); + cl_git_pass(git_index_read_index(newindex, index)); + + cl_assert(entry = git_index_get_bypath(newindex, "A", 0)); + cl_assert_equal_i(0, entry->file_size); + + git_index_free(index); + git_index_free(newindex); +} + +void test_index_racy__read_index_clears_uptodate_bit(void) +{ + git_index *index, *newindex; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_new(&newindex)); + cl_git_pass(git_index_read_index(newindex, index)); + + /* ensure that files brought in from the other index are not uptodate */ + cl_assert((entry = git_index_get_bypath(newindex, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(newindex, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(newindex, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_IDXENTRY_UPTODATE)); + + git_index_free(index); + git_index_free(newindex); +} diff --git a/tests/index/rename.c b/tests/index/rename.c index dd3cfa732..86eaf0053 100644 --- a/tests/index/rename.c +++ b/tests/index/rename.c @@ -48,3 +48,39 @@ void test_index_rename__single_file(void) cl_fixture_cleanup("rename"); } + +void test_index_rename__casechanging(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + git_index_entry new = {{0}}; + + p_mkdir("rename", 0700); + + cl_git_pass(git_repository_init(&repo, "./rename", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); + + cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); + cl_assert_equal_i(1, git_index_entrycount(index)); + cl_assert((entry = git_index_get_bypath(index, "lame.name.txt", 0))); + + memcpy(&new, entry, sizeof(git_index_entry)); + new.path = "LAME.name.TXT"; + + cl_git_pass(git_index_add(index, &new)); + cl_assert((entry = git_index_get_bypath(index, "LAME.name.TXT", 0))); + + if (cl_repo_get_bool(repo, "core.ignorecase")) + cl_assert_equal_i(1, git_index_entrycount(index)); + else + cl_assert_equal_i(2, git_index_entrycount(index)); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("rename"); +} + diff --git a/tests/index/tests.c b/tests/index/tests.c index e1ff12ad0..1498196b2 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -155,6 +155,27 @@ void test_index_tests__find_in_empty(void) git_index_free(index); } +void test_index_tests__find_prefix(void) +{ + git_index *index; + const git_index_entry *entry; + size_t pos; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + + cl_git_pass(git_index_find_prefix(&pos, index, "src")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/block-sha1/sha1.c") == 0); + + cl_git_pass(git_index_find_prefix(&pos, index, "src/co")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/commit.c") == 0); + + cl_assert(GIT_ENOTFOUND == git_index_find_prefix(NULL, index, "blah")); + + git_index_free(index); +} + void test_index_tests__write(void) { git_index *index; @@ -731,7 +752,7 @@ void test_index_tests__reload_from_disk(void) cl_set_cleanup(&cleanup_myrepo, NULL); - cl_git_pass(git_futils_mkdir("./myrepo", NULL, 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("./myrepo", 0777, GIT_MKDIR_PATH)); cl_git_mkfile("./myrepo/a.txt", "a\n"); cl_git_mkfile("./myrepo/b.txt", "b\n"); @@ -792,10 +813,43 @@ void test_index_tests__reload_while_ignoring_case(void) cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEXCAP_IGNORE_CASE)); cl_git_pass(git_index_read(index, true)); cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, ".header", 0)); cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); cl_git_pass(git_index_read(index, true)); cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert(git_index_get_bypath(index, ".header", 0)); + + git_index_free(index); +} + +void test_index_tests__change_icase_on_instance(void) +{ + git_index *index; + unsigned int caps; + const git_index_entry *e; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + + caps = git_index_caps(index); + cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEXCAP_IGNORE_CASE)); + cl_assert_equal_i(false, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "src/common.h", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "SRC/Common.h", 0)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "copying", 0)); + + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); + cl_assert_equal_i(true, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_s("COPYING", e->path); + cl_assert(e = git_index_get_bypath(index, "copying", 0)); + cl_assert_equal_s("COPYING", e->path); git_index_free(index); } diff --git a/tests/main.c b/tests/main.c index 56326da1c..f67c8ffbc 100644 --- a/tests/main.c +++ b/tests/main.c @@ -1,10 +1,3 @@ - -#if defined(GIT_MSVC_CRTDBG) -/* Enable MSVC CRTDBG memory leak reporting. See src/util.h for details. */ -#include -#include -#endif - #include "clar_libgit2.h" #include "clar_libgit2_trace.h" @@ -16,18 +9,6 @@ int main(int argc, char *argv[]) { int res; -#if defined(GIT_MSVC_CRTDBG) - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); -#endif - clar_test_init(argc, argv); git_libgit2_init(); diff --git a/tests/merge/conflict_data.h b/tests/merge/conflict_data.h new file mode 100644 index 000000000..e6394a9e8 --- /dev/null +++ b/tests/merge/conflict_data.h @@ -0,0 +1,103 @@ +#define AUTOMERGEABLE_MERGED_FILE \ + "this file is changed in master\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is changed in branch\n" + +#define AUTOMERGEABLE_MERGED_FILE_CRLF \ + "this file is changed in master\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is changed in branch\r\n" + +#define CONFLICTING_MERGE_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +#define CONFLICTING_DIFF3_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "||||||| initial\n" \ + "this file is a conflict\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +#define CONFLICTING_UNION_FILE \ + "this file is changed in master and branch\n" \ + "this file is changed in branch and master\n" + +#define CONFLICTING_RECURSIVE_F1_TO_F2 \ + "VEAL SOUP.\n" \ + "\n" \ + "<<<<<<< HEAD\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 not too small, one\n" \ + ">>>>>>> branchF-2\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" \ + "longer; take out the slices of ham, and skim off the grease if any\n" \ + "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "stir it well, and pour it into the pot, continuing to stir until it has\n" \ + "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + "in, first taking off their skins, by letting them stand a few minutes in\n" \ + "hot water, when they may be easily peeled. When made in this way you\n" \ + "must thicken it with the flour only. Any part of the veal may be used,\n" \ + "but the shin or knuckle is the nicest.\n" \ + "\n" \ + "<<<<<<< HEAD\n" \ + "This certainly is a mighty fine recipe.\n" \ + "=======\n" \ + "This is a mighty fine recipe!\n" \ + ">>>>>>> branchF-2\n" + +#define CONFLICTING_RECURSIVE_H1_TO_H2_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" \ + "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" \ + "longer; take out the slices of ham, and skim off the grease if any\n" \ + "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "stir it well, and pour it into the pot, continuing to stir until it has\n" \ + "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + "in, first taking off their skins, by letting them stand a few minutes in\n" \ + "hot water, when they may be easily peeled. When made in this way you\n" \ + "must thicken it with the flour only. Any part of the veal may be used,\n" \ + "but the shin or knuckle is the nicest.\n" diff --git a/tests/merge/files.c b/tests/merge/files.c index 2fd90d066..daa73fada 100644 --- a/tests/merge/files.c +++ b/tests/merge/files.c @@ -4,8 +4,10 @@ #include "buffer.h" #include "merge.h" #include "merge_helpers.h" +#include "conflict_data.h" #include "refs.h" #include "fileops.h" +#include "diff_xdiff.h" #define TEST_REPO_PATH "merge-resolve" #define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" @@ -288,3 +290,90 @@ void test_merge_files__doesnt_add_newline(void) git_merge_file_result_free(&result); } +void test_merge_files__skips_large_files(void) +{ + git_merge_file_input 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}; + + ours.size = GIT_XDIFF_MAX_SIZE + 1; + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.size = GIT_XDIFF_MAX_SIZE + 1; + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + cl_git_pass(git_merge_file(&result, NULL, &ours, &theirs, &opts)); + + cl_assert_equal_i(0, result.automergeable); + + git_merge_file_result_free(&result); +} + +void test_merge_files__skips_binaries(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_result result = {0}; + + ancestor.ptr = "ance\0stor\0"; + ancestor.size = 10; + ancestor.path = "ancestor.txt"; + ancestor.mode = 0100755; + + ours.ptr = "foo\0bar\0"; + ours.size = 8; + ours.path = "ours.txt"; + ours.mode = 0100755; + + theirs.ptr = "bar\0foo\0"; + theirs.size = 8; + theirs.path = "theirs.txt"; + theirs.mode = 0100644; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL)); + + cl_assert_equal_i(0, result.automergeable); + + git_merge_file_result_free(&result); +} + +void test_merge_files__handles_binaries_when_favored(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}; + + ancestor.ptr = "ance\0stor\0"; + ancestor.size = 10; + ancestor.path = "ancestor.txt"; + ancestor.mode = 0100755; + + ours.ptr = "foo\0bar\0"; + ours.size = 8; + ours.path = "ours.txt"; + ours.mode = 0100755; + + theirs.ptr = "bar\0foo\0"; + theirs.size = 8; + theirs.path = "theirs.txt"; + theirs.mode = 0100644; + + opts.favor = GIT_MERGE_FILE_FAVOR_OURS; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("ours.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(ours.size, result.len); + cl_assert(memcmp(result.ptr, ours.ptr, ours.size) == 0); + + git_merge_file_result_free(&result); +} diff --git a/tests/merge/merge_helpers.c b/tests/merge/merge_helpers.c index f81471424..4b1b7d262 100644 --- a/tests/merge/merge_helpers.c +++ b/tests/merge/merge_helpers.c @@ -4,6 +4,7 @@ #include "tree.h" #include "merge_helpers.h" #include "merge.h" +#include "index.h" #include "git2/merge.h" #include "git2/sys/index.h" #include "git2/annotated_commit.h" @@ -40,7 +41,7 @@ int merge_trees_from_branches( cl_git_pass(git_commit_tree(&our_tree, our_commit)); cl_git_pass(git_commit_tree(&their_tree, their_commit)); - cl_git_pass(git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, opts)); + error = git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, opts); git_buf_free(&branch_buf); git_tree_free(our_tree); @@ -50,7 +51,7 @@ int merge_trees_from_branches( git_commit_free(their_commit); git_commit_free(ancestor_commit); - return 0; + return error; } int merge_commits_from_branches( @@ -61,6 +62,7 @@ int merge_commits_from_branches( git_commit *our_commit, *their_commit; git_oid our_oid, their_oid; git_buf branch_buf = GIT_BUF_INIT; + int error; git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name); cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); @@ -71,13 +73,13 @@ int merge_commits_from_branches( cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); - cl_git_pass(git_merge_commits(index, repo, our_commit, their_commit, opts)); + error = git_merge_commits(index, repo, our_commit, their_commit, opts); git_buf_free(&branch_buf); git_commit_free(our_commit); git_commit_free(their_commit); - return 0; + return error; } int merge_branches(git_repository *repo, @@ -238,7 +240,7 @@ int merge_test_index(git_index *index, const struct merge_index_entry expected[] const git_index_entry *index_entry; /* - dump_index_entries(&index->entries); + merge__dump_index_entries(&index->entries); */ if (git_index_entrycount(index) != expected_len) diff --git a/tests/merge/merge_helpers.h b/tests/merge/merge_helpers.h index 554c24b7c..e407c7d13 100644 --- a/tests/merge/merge_helpers.h +++ b/tests/merge/merge_helpers.h @@ -4,49 +4,6 @@ #include "merge.h" #include "git2/merge.h" -#define AUTOMERGEABLE_MERGED_FILE \ - "this file is changed in master\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is changed in branch\n" - -#define AUTOMERGEABLE_MERGED_FILE_CRLF \ - "this file is changed in master\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is changed in branch\r\n" - -#define CONFLICTING_MERGE_FILE \ - "<<<<<<< HEAD\n" \ - "this file is changed in master and branch\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" - -#define CONFLICTING_DIFF3_FILE \ - "<<<<<<< HEAD\n" \ - "this file is changed in master and branch\n" \ - "||||||| initial\n" \ - "this file is a conflict\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" - -#define CONFLICTING_UNION_FILE \ - "this file is changed in master and branch\n" \ - "this file is changed in branch and master\n" - - struct merge_index_entry { uint16_t mode; char oid_str[GIT_OID_HEXSZ+1]; diff --git a/tests/merge/trees/automerge.c b/tests/merge/trees/automerge.c index c18881d7c..67f2cf786 100644 --- a/tests/merge/trees/automerge.c +++ b/tests/merge/trees/automerge.c @@ -3,8 +3,9 @@ #include "git2/merge.h" #include "buffer.h" #include "merge.h" -#include "../merge_helpers.h" #include "fileops.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" static git_repository *repo; diff --git a/tests/merge/trees/commits.c b/tests/merge/trees/commits.c index c4e470997..786a77a8b 100644 --- a/tests/merge/trees/commits.c +++ b/tests/merge/trees/commits.c @@ -3,6 +3,7 @@ #include "git2/merge.h" #include "merge.h" #include "../merge_helpers.h" +#include "../conflict_data.h" static git_repository *repo; @@ -94,7 +95,6 @@ void test_merge_trees_commits__no_ancestor(void) git_index_free(index); } - void test_merge_trees_commits__df_conflict(void) { git_index *index; @@ -129,3 +129,20 @@ void test_merge_trees_commits__df_conflict(void) git_index_free(index); } + +void test_merge_trees_commits__fail_on_conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + opts.flags |= GIT_MERGE_FAIL_ON_CONFLICT; + + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); + + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_commits_from_branches(&index, repo, "master", "unrelated", &opts)); + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_commits_from_branches(&index, repo, "master", "branch", &opts)); +} + diff --git a/tests/merge/trees/recursive.c b/tests/merge/trees/recursive.c new file mode 100644 index 000000000..c5b129bf8 --- /dev/null +++ b/tests/merge/trees/recursive.c @@ -0,0 +1,410 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_merge_trees_recursive__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_recursive__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_recursive__one_base_commit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__one_base_commit_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_base_commits(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "666ffdfcf1eaa5641fa31064bf2607327e843c09", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_base_commits_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "cb49ad76147f5f9439cbd6133708b76142660660", 1, "veal.txt" }, + { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 2, "veal.txt" }, + { 0100644, "4e21d2d63357bde5027d1625f5ec6b430cdeb143", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_levels_of_multiple_bases(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "15faa0c9991f2d65686e844651faa2ff9827887b", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_levels_of_multiple_bases_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 1, "veal.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, + { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + 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); +} + +void test_merge_trees_recursive__three_levels_of_multiple_bases(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "d55e5dc038c52f1a36548625bcb666cbc06db9e6", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_levels_of_multiple_bases_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 1, "veal.txt" }, + { 0100644, "f1b44c04989a3a1c14b036cfadfa328d53a7bc5e", 2, "veal.txt" }, + { 0100644, "5e8747f5200fac0f945a07daf6163ca9cb1a8da9", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_base_commits(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4f7269b07c76d02755d75ccaf05c0b4c36cdc6c", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_base_commits_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "9e12bce04446d097ae1782967a5888c2e2a0d35b", 1, "gravy.txt" }, + { 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" }, + { 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, + { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, + { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchF-1", "branchF-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* + * Branch G-1 and G-2 have three common ancestors (815b5a1, ad2ace9, 483065d). + * The merge-base of the first two has two common ancestors (723181f, a34e5a1) + * which themselves have two common ancestors (8f35f30, 3a3f5a6), which + * finally has a common ancestor of 7c7bf85. This virtual merge base will + * be computed and merged with 483065d which also has a common ancestor of + * 7c7bf85. + */ +void test_merge_trees_recursive__oh_so_many_levels_of_recursion(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchG-1", "branchG-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict. + */ +void test_merge_trees_recursive__conflicting_merge_base(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "3a66812fed1e03ea4a6a7ee28d8a57aec1ca6537", 1, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict. The generated common ancestor file will + * have diff3 style conflicts inside it. + */ +void test_merge_trees_recursive__conflicting_merge_base_with_diff3(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 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" }, + }; + + opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* Branch I-1 and I-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict, but when each was merged, the conflicts were + * resolved identically, thus merging I-1 into I-2 does not conflict. + */ +void test_merge_trees_recursive__conflicting_merge_base_since_resolved(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a02d4fd126e0cc8fb46ee48cf38bad36d44f2dbc", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchI-1", "branchI-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +/* There are multiple levels of criss-cross merges, and multiple recursive + * merges would create a common ancestor that allows the merge to complete + * successfully. Test that we can build a single virtual base, then stop, + * which will produce a conflicting merge. + */ +void test_merge_trees_recursive__recursionlimit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 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, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + opts.recursion_limit = 1; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c index b96c4c4db..3634568de 100644 --- a/tests/merge/trees/treediff.c +++ b/tests/merge/trees/treediff.c @@ -44,9 +44,10 @@ static void test_find_differences( git_oid ancestor_oid, ours_oid, theirs_oid; git_tree *ancestor_tree, *ours_tree, *theirs_tree; git_iterator *ancestor_iter, *ours_iter, *theirs_iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.flags |= GIT_MERGE_FIND_RENAMES; opts.target_limit = 1000; opts.rename_threshold = 50; @@ -67,12 +68,11 @@ static void test_find_differences( cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid)); cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid)); - cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); - cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); - cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, &iter_opts)); cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_iter, ours_iter, theirs_iter)); cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts)); diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c index 4bf984c23..99e33e0cd 100644 --- a/tests/merge/workdir/dirty.c +++ b/tests/merge/workdir/dirty.c @@ -133,7 +133,7 @@ static void hack_index(char *files[]) struct stat statbuf; git_buf path = GIT_BUF_INIT; git_index_entry *entry; - struct timeval times[2]; + struct p_timeval times[2]; time_t now; size_t i; @@ -162,15 +162,20 @@ static void hack_index(char *files[]) cl_git_pass(p_utimes(path.ptr, times)); cl_git_pass(p_stat(path.ptr, &statbuf)); - entry->ctime.seconds = (git_time_t)statbuf.st_ctime; + entry->ctime.seconds = (int32_t)statbuf.st_ctime; + entry->mtime.seconds = (int32_t)statbuf.st_mtime; +#if defined(GIT_USE_NSEC) + entry->ctime.nanoseconds = statbuf.st_ctim.tv_nsec; + entry->mtime.nanoseconds = statbuf.st_mtim.tv_nsec; +#else entry->ctime.nanoseconds = 0; - entry->mtime.seconds = (git_time_t)statbuf.st_mtime; entry->mtime.nanoseconds = 0; +#endif entry->dev = statbuf.st_dev; entry->ino = statbuf.st_ino; entry->uid = statbuf.st_uid; entry->gid = statbuf.st_gid; - entry->file_size = statbuf.st_size; + entry->file_size = (uint32_t)statbuf.st_size; } git_buf_free(&path); diff --git a/tests/merge/workdir/recursive.c b/tests/merge/workdir/recursive.c new file mode 100644 index 000000000..795126255 --- /dev/null +++ b/tests/merge/workdir/recursive.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_merge_workdir_recursive__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_workdir_recursive__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_workdir_recursive__writes_conflict_with_virtual_base(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_buf conflicting_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, + { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, + { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, + }; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchF-1", GIT_REFS_HEADS_DIR "branchF-2", &opts, NULL)); + + 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_F1_TO_F2, conflicting_buf.ptr); + + git_index_free(index); + git_buf_free(&conflicting_buf); +} + +void test_merge_workdir_recursive__conflicting_merge_base_with_diff3(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_buf conflicting_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 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" }, + }; + + 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(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); + + git_index_free(index); + git_buf_free(&conflicting_buf); +} diff --git a/tests/merge/workdir/renames.c b/tests/merge/workdir/renames.c index 83006a703..fabcda2a8 100644 --- a/tests/merge/workdir/renames.c +++ b/tests/merge/workdir/renames.c @@ -63,7 +63,7 @@ void test_merge_workdir_renames__renames(void) { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, }; - merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; merge_opts.rename_threshold = 50; cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); @@ -99,7 +99,7 @@ void test_merge_workdir_renames__ours(void) { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" }, }; - merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; merge_opts.rename_threshold = 50; checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; @@ -147,7 +147,7 @@ void test_merge_workdir_renames__similar(void) { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, }; - merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; merge_opts.rename_threshold = 50; cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c index abc0777f7..3cdd15b5a 100644 --- a/tests/merge/workdir/simple.c +++ b/tests/merge/workdir/simple.c @@ -4,6 +4,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" +#include "../conflict_data.h" #include "refs.h" #include "fileops.h" diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 06ee3dd36..17c8f26e3 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -369,17 +369,46 @@ void test_network_fetchlocal__clone_into_mirror(void) { git_clone_options opts = GIT_CLONE_OPTIONS_INIT; git_repository *repo; - git_reference *head; + git_reference *ref; opts.bare = true; opts.remote_cb = remote_mirror_cb; cl_git_pass(git_clone(&repo, cl_git_fixture_url("testrepo.git"), "./foo.git", &opts)); - cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); - cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + cl_git_pass(git_reference_lookup(&ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(ref)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(ref)); - git_reference_free(head); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); + + git_reference_free(ref); + git_repository_free(repo); + cl_fixture_cleanup("./foo.git"); +} + +void test_network_fetchlocal__all_refs(void) +{ + git_repository *repo; + git_remote *remote; + git_reference *ref; + char *allrefs = "+refs/*:refs/*"; + git_strarray refspecs = { + &allrefs, + 1, + }; + + cl_git_pass(git_repository_init(&repo, "./foo.git", true)); + cl_git_pass(git_remote_create_anonymous(&remote, repo, cl_git_fixture_url("testrepo.git"))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/test")); + git_reference_free(ref); + + git_remote_free(remote); git_repository_free(repo); cl_fixture_cleanup("./foo.git"); } diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c index e83755ef6..5edd79fb8 100644 --- a/tests/network/remote/defaultbranch.c +++ b/tests/network/remote/defaultbranch.c @@ -26,7 +26,7 @@ static void assert_default_branch(const char *should) { git_buf name = GIT_BUF_INIT; - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_pass(git_remote_default_branch(&name, g_remote)); cl_assert_equal_s(should, name.ptr); git_buf_free(&name); @@ -57,7 +57,7 @@ void test_network_remote_defaultbranch__no_default_branch(void) git_buf buf = GIT_BUF_INIT; cl_git_pass(git_remote_create(&remote_b, g_repo_b, "self", git_repository_path(g_repo_b))); - cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_pass(git_remote_ls(&heads, &len, remote_b)); cl_assert_equal_i(0, len); @@ -80,7 +80,7 @@ void test_network_remote_defaultbranch__detached_sharing_nonbranch_id(void) cl_git_pass(git_reference_create(&ref, g_repo_a, "refs/foo/bar", &id, 1, NULL)); git_reference_free(ref); - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, g_remote)); cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./local-detached", NULL)); diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c index 5d726c958..4d990ab71 100644 --- a/tests/network/remote/local.c +++ b/tests/network/remote/local.c @@ -40,7 +40,7 @@ static void connect_to_local_repository(const char *local_repository) git_buf_sets(&file_path_buf, cl_git_path_url(local_repository)); cl_git_pass(git_remote_create_anonymous(&remote, repo, git_buf_cstr(&file_path_buf))); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); } void test_network_remote_local__connected(void) @@ -214,7 +214,7 @@ void test_network_remote_local__push_to_bare_remote(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localbare.git")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); /* Try to push */ cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); @@ -253,7 +253,7 @@ void test_network_remote_local__push_to_bare_remote_with_file_url(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, url)); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); /* Try to push */ cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); @@ -290,7 +290,7 @@ void test_network_remote_local__push_to_non_bare_remote(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localnonbare")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); /* Try to push */ cl_git_fail_with(GIT_EBAREREPO, git_remote_upload(localremote, &push_array, NULL)); diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c index 2fa21d460..46abc6d33 100644 --- a/tests/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -93,7 +93,7 @@ void test_network_remote_remotes__error_when_no_push_available(void) cl_git_pass(git_remote_create_anonymous(&r, _repo, cl_fixture("testrepo.git"))); callbacks.transport = git_transport_local; - cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks)); + cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, NULL)); /* Make sure that push is really not available */ r->transport->push = NULL; @@ -359,7 +359,7 @@ void test_network_remote_remotes__can_load_with_an_empty_url(void) cl_assert(remote->url == NULL); cl_assert(remote->pushurl == NULL); - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_assert(giterr_last() != NULL); cl_assert(giterr_last()->klass == GITERR_INVALID); @@ -376,7 +376,7 @@ void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) cl_assert(remote->url == NULL); cl_assert(remote->pushurl == NULL); - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); git_remote_free(remote); } diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index 5433e5f03..a9decf9c1 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -18,6 +18,8 @@ void test_object_tree_write__initialize(void) void test_object_tree_write__cleanup(void) { cl_git_sandbox_cleanup(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); } void test_object_tree_write__from_memory(void) @@ -131,15 +133,18 @@ void test_object_tree_write__sorted_subtrees(void) { GIT_FILEMODE_TREE, "vendors"} }; - git_oid blank_oid, tree_oid; + git_oid bid, tid, tree_oid; - memset(&blank_oid, 0x0, sizeof(blank_oid)); + cl_git_pass(git_oid_fromstr(&bid, blob_oid)); + cl_git_pass(git_oid_fromstr(&tid, first_tree)); cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); for (i = 0; i < ARRAY_SIZE(entries); ++i) { + git_oid *id = entries[i].attr == GIT_FILEMODE_TREE ? &tid : &bid; + cl_git_pass(git_treebuilder_insert(NULL, - builder, entries[i].filename, &blank_oid, entries[i].attr)); + builder, entries[i].filename, id, entries[i].attr)); } cl_git_pass(git_treebuilder_write(&tree_oid, builder)); @@ -187,10 +192,10 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) { git_treebuilder *builder; int i, aardvark_i, apple_i, apple_after_i, apple_extra_i, last_i; - git_oid blank_oid, tree_oid; + git_oid entry_oid, tree_oid; git_tree *tree; - memset(&blank_oid, 0x0, sizeof(blank_oid)); + cl_git_pass(git_oid_fromstr(&entry_oid, blob_oid)); cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); @@ -198,7 +203,7 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) for (i = 0; _entries[i].filename; ++i) cl_git_pass(git_treebuilder_insert(NULL, - builder, _entries[i].filename, &blank_oid, _entries[i].attr)); + builder, _entries[i].filename, &entry_oid, _entries[i].attr)); cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); @@ -209,12 +214,12 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder)); cl_git_pass(git_treebuilder_insert( - NULL, builder, "before_last", &blank_oid, GIT_FILEMODE_BLOB)); + NULL, builder, "before_last", &entry_oid, GIT_FILEMODE_BLOB)); cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); /* reinsert apple_after */ cl_git_pass(git_treebuilder_insert( - NULL, builder, "apple_after", &blank_oid, GIT_FILEMODE_BLOB)); + NULL, builder, "apple_after", &entry_oid, GIT_FILEMODE_BLOB)); cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); cl_git_pass(git_treebuilder_remove(builder, "last")); @@ -222,11 +227,11 @@ void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) /* reinsert last */ cl_git_pass(git_treebuilder_insert( - NULL, builder, "last", &blank_oid, GIT_FILEMODE_BLOB)); + NULL, builder, "last", &entry_oid, GIT_FILEMODE_BLOB)); cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); cl_git_pass(git_treebuilder_insert( - NULL, builder, "apple_extra", &blank_oid, GIT_FILEMODE_BLOB)); + NULL, builder, "apple_extra", &entry_oid, GIT_FILEMODE_BLOB)); cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder)); cl_git_pass(git_treebuilder_write(&tree_oid, builder)); @@ -278,16 +283,16 @@ void test_object_tree_write__filtering(void) { git_treebuilder *builder; int i; - git_oid blank_oid, tree_oid; + git_oid entry_oid, tree_oid; git_tree *tree; - memset(&blank_oid, 0x0, sizeof(blank_oid)); + git_oid_fromstr(&entry_oid, blob_oid); cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); for (i = 0; _entries[i].filename; ++i) cl_git_pass(git_treebuilder_insert(NULL, - builder, _entries[i].filename, &blank_oid, _entries[i].attr)); + builder, _entries[i].filename, &entry_oid, _entries[i].attr)); cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); @@ -406,6 +411,8 @@ void test_object_tree_write__protect_filesystems(void) git_treebuilder *builder; git_oid bid; + cl_git_pass(git_oid_fromstr(&bid, "fa49b077972391ad58037050f2a75f74e3671e92")); + /* Ensure that (by default) we can write objects with funny names on * platforms that are not affected. */ @@ -440,3 +447,68 @@ void test_object_tree_write__protect_filesystems(void) git_treebuilder_free(builder); } + +static void test_invalid_objects(bool should_allow_invalid) +{ + git_treebuilder *builder; + git_oid valid_blob_id, invalid_blob_id, valid_tree_id, invalid_tree_id; + +#define assert_allowed(expr) \ + clar__assert(!(expr) == should_allow_invalid, __FILE__, __LINE__, \ + (should_allow_invalid ? \ + "Expected function call to succeed: " #expr : \ + "Expected function call to fail: " #expr), \ + NULL, 1) + + cl_git_pass(git_oid_fromstr(&valid_blob_id, blob_oid)); + cl_git_pass(git_oid_fromstr(&invalid_blob_id, + "1234567890123456789012345678901234567890")); + cl_git_pass(git_oid_fromstr(&valid_tree_id, first_tree)); + cl_git_pass(git_oid_fromstr(&invalid_tree_id, + "0000000000111111111122222222223333333333")); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + /* test valid blobs and trees (these should always pass) */ + cl_git_pass(git_treebuilder_insert(NULL, builder, "file.txt", &valid_blob_id, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "folder", &valid_tree_id, GIT_FILEMODE_TREE)); + + /* replace valid files and folders with invalid ones */ + assert_allowed(git_treebuilder_insert(NULL, builder, "file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); + assert_allowed(git_treebuilder_insert(NULL, builder, "folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); + + /* insert new invalid files and folders */ + assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); + assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); + + /* insert valid blobs as trees and trees as blobs */ + assert_allowed(git_treebuilder_insert(NULL, builder, "file_as_folder", &valid_blob_id, GIT_FILEMODE_TREE)); + assert_allowed(git_treebuilder_insert(NULL, builder, "folder_as_file.txt", &valid_tree_id, GIT_FILEMODE_BLOB)); + +#undef assert_allowed + + git_treebuilder_free(builder); +} + +static void test_inserting_submodule(void) +{ + git_treebuilder *bld; + git_oid sm_id; + + 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); +} + +void test_object_tree_write__object_validity(void) +{ + /* Ensure that we cannot add invalid objects by default */ + test_invalid_objects(false); + test_inserting_submodule(); + + /* Ensure that we can turn off validation */ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + test_invalid_objects(true); + test_inserting_submodule(); +} + diff --git a/tests/odb/alternates.c b/tests/odb/alternates.c index c75f6feaa..b5c0e79c0 100644 --- a/tests/odb/alternates.c +++ b/tests/odb/alternates.c @@ -29,7 +29,7 @@ static void init_linked_repo(const char *path, const char *alternate) cl_git_pass(git_path_prettify(&destpath, alternate, NULL)); cl_git_pass(git_buf_joinpath(&destpath, destpath.ptr, "objects")); cl_git_pass(git_buf_joinpath(&filepath, git_repository_path(repo), "objects/info")); - cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(filepath.ptr, 0755, GIT_MKDIR_PATH)); cl_git_pass(git_buf_joinpath(&filepath, filepath.ptr , "alternates")); cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0, 0666)); diff --git a/tests/odb/sorting.c b/tests/odb/sorting.c index 147a160c8..6af8b0d1b 100644 --- a/tests/odb/sorting.c +++ b/tests/odb/sorting.c @@ -14,6 +14,7 @@ static git_odb_backend *new_backend(size_t position) if (b == NULL) return NULL; + b->base.free = (void (*)(git_odb_backend *)) git__free; b->base.version = GIT_ODB_BACKEND_VERSION; b->position = position; return (git_odb_backend *)b; @@ -56,14 +57,14 @@ void test_odb_sorting__basic_backends_sorting(void) void test_odb_sorting__alternate_backends_sorting(void) { - cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 5)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 3)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(5), 4)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(7), 1)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 5)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(5), 3)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 4)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(7), 1)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(0), 5)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 3)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(2), 4)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 1)); check_backend_sorting(_odb); } diff --git a/tests/online/badssl.c b/tests/online/badssl.c new file mode 100644 index 000000000..12badbda3 --- /dev/null +++ b/tests/online/badssl.c @@ -0,0 +1,38 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" + +static git_repository *g_repo; + +#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) +static bool g_has_ssl = true; +#else +static bool g_has_ssl = false; +#endif + +void test_online_badssl__expired(void) +{ + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); +} + +void test_online_badssl__wrong_host(void) +{ + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); +} + +void test_online_badssl__self_signed(void) +{ + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL)); +} diff --git a/tests/online/clone.c b/tests/online/clone.c index e63cf55f1..b84be405c 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -17,6 +17,15 @@ static git_repository *g_repo; static git_clone_options g_options; +static char *_remote_url = NULL; +static char *_remote_user = NULL; +static char *_remote_pass = NULL; +static char *_remote_ssh_pubkey = NULL; +static char *_remote_ssh_privkey = NULL; +static char *_remote_ssh_passphrase = NULL; +static char *_remote_ssh_fingerprint = NULL; + + void test_online_clone__initialize(void) { git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT; @@ -29,6 +38,14 @@ void test_online_clone__initialize(void) g_options.checkout_opts = dummy_opts; g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_options.fetch_opts = dummy_fetch; + + _remote_url = cl_getenv("GITTEST_REMOTE_URL"); + _remote_user = cl_getenv("GITTEST_REMOTE_USER"); + _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); + _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + _remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + _remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"); } void test_online_clone__cleanup(void) @@ -38,6 +55,14 @@ void test_online_clone__cleanup(void) g_repo = NULL; } cl_fixture_cleanup("./foo"); + + git__free(_remote_url); + git__free(_remote_user); + git__free(_remote_pass); + git__free(_remote_ssh_pubkey); + git__free(_remote_ssh_privkey); + git__free(_remote_ssh_passphrase); + git__free(_remote_ssh_fingerprint); } void test_online_clone__network_full(void) @@ -188,6 +213,33 @@ void test_online_clone__custom_remote_callbacks(void) cl_assert(callcount > 0); } +void test_online_clone__custom_headers(void) +{ + char *empty_header = ""; + char *unnamed_header = "this is a header about nothing"; + char *newlines = "X-Custom: almost OK\n"; + char *conflict = "Accept: defined-by-git"; + char *ok = "X-Custom: this should be ok"; + + g_options.fetch_opts.custom_headers.count = 1; + + g_options.fetch_opts.custom_headers.strings = &empty_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &unnamed_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &newlines; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &conflict; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + /* Finally, we got it right! */ + g_options.fetch_opts.custom_headers.strings = &ok; + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); +} + static int cred_failure_cb( git_cred **cred, const char *url, @@ -202,15 +254,12 @@ static int cred_failure_cb( void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) { - const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); - - if (!remote_url || !remote_user) + if (!_remote_url || !_remote_user) clar__skip(); g_options.fetch_opts.callbacks.credentials = cred_failure_cb; - cl_git_fail_with(-172, git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options)); } static int cred_count_calls_cb(git_cred **cred, const char *url, const char *user, @@ -233,17 +282,15 @@ static int cred_count_calls_cb(git_cred **cred, const char *url, const char *use void test_online_clone__cred_callback_called_again_on_auth_failure(void) { - const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); size_t counter = 0; - if (!remote_url || !remote_user) + if (!_remote_url || !_remote_user) clar__skip(); g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; g_options.fetch_opts.callbacks.payload = &counter; - cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); cl_assert_equal_i(3, counter); } @@ -269,22 +316,22 @@ void test_online_clone__credentials(void) /* Remote URL environment variable must be set. * User and password are optional. */ - const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); git_cred_userpass_payload user_pass = { - cl_getenv("GITTEST_REMOTE_USER"), - cl_getenv("GITTEST_REMOTE_PASS") + _remote_user, + _remote_pass }; - if (!remote_url) return; + if (!_remote_url) + clar__skip(); - if (cl_getenv("GITTEST_REMOTE_DEFAULT")) { + if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) { g_options.fetch_opts.callbacks.credentials = cred_default; } else { g_options.fetch_opts.callbacks.credentials = git_cred_userpass; g_options.fetch_opts.callbacks.payload = &user_pass; } - cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; cl_fixture_cleanup("./foo"); } @@ -335,18 +382,15 @@ void test_online_clone__can_cancel(void) static int cred_cb(git_cred **cred, const char *url, const char *user_from_url, unsigned int allowed_types, void *payload) { - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); - const char *pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); - const char *privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); - const char *passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); - GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); if (allowed_types & GIT_CREDTYPE_USERNAME) - return git_cred_username_new(cred, remote_user); + return git_cred_username_new(cred, _remote_user); if (allowed_types & GIT_CREDTYPE_SSH_KEY) - return git_cred_ssh_key_new(cred, remote_user, pubkey, privkey, passphrase); + return git_cred_ssh_key_new(cred, + _remote_user, _remote_ssh_pubkey, + _remote_ssh_privkey, _remote_ssh_passphrase); giterr_set(GITERR_NET, "unexpected cred type"); return -1; @@ -417,13 +461,10 @@ void test_online_clone__ssh_with_paths(void) 2, }; - const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); - #ifndef GIT_SSH clar__skip(); #endif - if (!remote_url || !remote_user || strncmp(remote_url, "ssh://", 5) != 0) + if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0) clar__skip(); g_options.remote_cb = custom_remote_ssh_with_paths; @@ -431,10 +472,10 @@ void test_online_clone__ssh_with_paths(void) g_options.fetch_opts.callbacks.credentials = cred_cb; g_options.fetch_opts.callbacks.payload = &arr; - cl_git_fail(git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options)); arr.strings = good_paths; - cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); } static int cred_foo_bar(git_cred **cred, const char *url, const char *username_from_url, @@ -460,15 +501,13 @@ int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *pay { git_cert_hostkey *key; git_oid expected = {{0}}, actual = {{0}}; - const char *expected_str; GIT_UNUSED(valid); GIT_UNUSED(payload); - expected_str = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"); - cl_assert(expected_str); + cl_assert(_remote_ssh_fingerprint); - cl_git_pass(git_oid_fromstrp(&expected, expected_str)); + cl_git_pass(git_oid_fromstrp(&expected, _remote_ssh_fingerprint)); cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type); key = (git_cert_hostkey *) cert; @@ -477,9 +516,9 @@ int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *pay * the type. Here we abuse the fact that both hashes fit into * our git_oid type. */ - if (strlen(expected_str) == 32 && key->type & GIT_CERT_SSH_MD5) { + if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) { memcpy(&actual.id, key->hash_md5, 16); - } else if (strlen(expected_str) == 40 && key->type & GIT_CERT_SSH_SHA1) { + } else if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) { memcpy(&actual, key->hash_sha1, 20); } else { cl_fail("Cannot find a usable SSH hash"); @@ -496,7 +535,7 @@ void test_online_clone__ssh_cert(void) { g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check; - if (!cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT")) + if (!_remote_ssh_fingerprint) cl_skip(); cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, "ssh://localhost/foo", "./foo", &g_options)); @@ -525,22 +564,17 @@ static char *read_key_file(const char *path) static int ssh_memory_cred_cb(git_cred **cred, const char *url, const char *user_from_url, unsigned int allowed_types, void *payload) { - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); - const char *pubkey_path = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); - const char *privkey_path = cl_getenv("GITTEST_REMOTE_SSH_KEY"); - const char *passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); - GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); if (allowed_types & GIT_CREDTYPE_USERNAME) - return git_cred_username_new(cred, remote_user); + return git_cred_username_new(cred, _remote_user); if (allowed_types & GIT_CREDTYPE_SSH_KEY) { - char *pubkey = read_key_file(pubkey_path); - char *privkey = read_key_file(privkey_path); + char *pubkey = read_key_file(_remote_ssh_pubkey); + char *privkey = read_key_file(_remote_ssh_privkey); - int ret = git_cred_ssh_key_memory_new(cred, remote_user, pubkey, privkey, passphrase); + int ret = git_cred_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase); if (privkey) free(privkey); @@ -555,19 +589,15 @@ static int ssh_memory_cred_cb(git_cred **cred, const char *url, const char *user void test_online_clone__ssh_memory_auth(void) { - const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); - const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); - const char *privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); - #ifndef GIT_SSH_MEMORY_CREDENTIALS clar__skip(); #endif - if (!remote_url || !remote_user || !privkey || strncmp(remote_url, "ssh://", 5) != 0) + if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0) clar__skip(); g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb; - cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); } void test_online_clone__url_with_no_path_returns_EINVALIDSPEC(void) diff --git a/tests/online/fetch.c b/tests/online/fetch.c index 72e7c24e3..c12df069f 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -81,11 +81,11 @@ void test_online_fetch__fetch_twice(void) { git_remote *remote; cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL); + git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); @@ -117,7 +117,7 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_assert_equal_i(false, invoked); @@ -155,7 +155,7 @@ void test_online_fetch__can_cancel(void) options.callbacks.transfer_progress = cancel_at_half; options.callbacks.payload = &bytes_received; - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_fail_with(git_remote_download(remote, NULL, &options), -4321); git_remote_disconnect(remote); git_remote_free(remote); @@ -169,7 +169,7 @@ void test_online_fetch__ls_disconnected(void) cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote)); git_remote_disconnect(remote); cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote)); @@ -187,7 +187,7 @@ void test_online_fetch__remote_symrefs(void) cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); git_remote_disconnect(remote); cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); diff --git a/tests/online/push.c b/tests/online/push.c index 6cd444320..77c437622 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -9,16 +9,16 @@ static git_repository *_repo; -static char *_remote_url; +static char *_remote_url = NULL; -static char *_remote_ssh_key; -static char *_remote_ssh_pubkey; -static char *_remote_ssh_passphrase; +static char *_remote_user = NULL; +static char *_remote_pass = NULL; -static char *_remote_user; -static char *_remote_pass; +static char *_remote_ssh_key = NULL; +static char *_remote_ssh_pubkey = NULL; +static char *_remote_ssh_passphrase = NULL; -static char *_remote_default; +static char *_remote_default = NULL; static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *); @@ -91,7 +91,6 @@ static int cred_acquire_cb( /** * git_push_status_foreach callback that records status entries. - * @param data (git_vector *) of push_status instances */ static int record_push_status_cb(const char *ref, const char *msg, void *payload) { @@ -299,7 +298,7 @@ static void verify_update_tips_callback(git_remote *remote, expected_ref expecte goto failed; } - if (git_oid_cmp(expected_refs[i].oid, tip->new_oid) != 0) { + if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) { git_buf_printf(&msg, "Updated tip ID does not match expected ID"); failed = 1; goto failed; @@ -355,6 +354,7 @@ void test_online_push__initialize(void) git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5"); /* Remote URL environment variable must be set. User and password are optional. */ + _remote_url = cl_getenv("GITTEST_REMOTE_URL"); _remote_user = cl_getenv("GITTEST_REMOTE_USER"); _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); @@ -372,7 +372,7 @@ void test_online_push__initialize(void) record_callbacks_data_clear(&_record_cbs_data); - cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs)); + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL)); /* Clean up previously pushed branches. Fails if receive.denyDeletes is * set on the remote. Also, on Git 1.7.0 and newer, you must run @@ -406,6 +406,14 @@ void test_online_push__cleanup(void) git_remote_free(_remote); _remote = NULL; + git__free(_remote_url); + git__free(_remote_user); + git__free(_remote_pass); + git__free(_remote_ssh_key); + git__free(_remote_ssh_pubkey); + git__free(_remote_ssh_passphrase); + git__free(_remote_default); + /* Freed by cl_git_sandbox_cleanup */ _repo = NULL; diff --git a/tests/online/push_util.c b/tests/online/push_util.c index cd483c7c0..eafec2f05 100644 --- a/tests/online/push_util.c +++ b/tests/online/push_util.c @@ -9,8 +9,6 @@ const git_oid OID_ZERO = {{ 0 }}; void updated_tip_free(updated_tip *t) { git__free(t->name); - git__free(t->old_oid); - git__free(t->new_oid); git__free(t); } @@ -46,14 +44,11 @@ int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid * updated_tip *t; record_callbacks_data *record_data = (record_callbacks_data *)data; - cl_assert(t = git__malloc(sizeof(*t))); + cl_assert(t = git__calloc(1, sizeof(*t))); cl_assert(t->name = git__strdup(refname)); - cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid))); - git_oid_cpy(t->old_oid, a); - - cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid))); - git_oid_cpy(t->new_oid, b); + git_oid_cpy(&t->old_oid, a); + git_oid_cpy(&t->new_oid, b); git_vector_insert(&record_data->updated_tips, t); diff --git a/tests/online/push_util.h b/tests/online/push_util.h index 822341bd2..570873cfe 100644 --- a/tests/online/push_util.h +++ b/tests/online/push_util.h @@ -16,8 +16,8 @@ extern const git_oid OID_ZERO; typedef struct { char *name; - git_oid *old_oid; - git_oid *new_oid; + git_oid old_oid; + git_oid new_oid; } updated_tip; typedef struct { diff --git a/tests/path/core.c b/tests/path/core.c index 064f1492a..3dccfe5fb 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -105,12 +105,12 @@ void test_path_core__isvalid_dot_git(void) cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.GIT/bar", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.Git", 0)); - cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); cl_assert_equal_b(true, git_path_isvalid(NULL, "!git", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/!git", 0)); diff --git a/tests/rebase/inmemory.c b/tests/rebase/inmemory.c new file mode 100644 index 000000000..d5d89c719 --- /dev/null +++ b/tests/rebase/inmemory.c @@ -0,0 +1,116 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "posix.h" + +#include + +static git_repository *repo; + +// Fixture setup and teardown +void test_rebase_inmemory__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); +} + +void test_rebase_inmemory__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_rebase_inmemory__not_in_rebase_state(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + 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)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + git_rebase_free(rebase); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); +} + +void test_rebase_inmemory__can_resolve_conflicts(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_status_list *status_list; + git_oid pick_id, commit_id, expected_commit_id; + git_signature *signature; + git_index *rebase_index, *repo_index; + git_index_entry resolution = {{0}}; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + cl_git_pass(git_signature_new(&signature, + "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + 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)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + + git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); + + cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); + cl_assert_equal_oid(&pick_id, &rebase_operation->id); + + /* ensure that we did not do anything stupid to the workdir or repo index */ + cl_git_pass(git_repository_index(&repo_index, repo)); + cl_assert(!git_index_has_conflicts(repo_index)); + + cl_git_pass(git_status_list_new(&status_list, repo, NULL)); + cl_assert_equal_i(0, git_status_list_entrycount(status_list)); + + /* but that the index returned from rebase does have conflicts */ + cl_git_pass(git_rebase_inmemory_index(&rebase_index, rebase)); + cl_assert(git_index_has_conflicts(rebase_index)); + + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + /* ensure that we can work with the in-memory index to resolve the conflict */ + resolution.path = "asparagus.txt"; + resolution.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&resolution.id, "414dfc71ead79c07acd4ea47fecf91f289afc4b9"); + cl_git_pass(git_index_conflict_remove(rebase_index, "asparagus.txt")); + cl_git_pass(git_index_add(rebase_index, &resolution)); + + /* and finally create a commit for the resolved rebase operation */ + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + cl_git_pass(git_oid_fromstr(&expected_commit_id, "db7af47222181e548810da2ab5fec0e9357c5637")); + cl_assert_equal_oid(&commit_id, &expected_commit_id); + + git_signature_free(signature); + git_status_list_free(status_list); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_index_free(repo_index); + git_index_free(rebase_index); + git_rebase_free(rebase); +} diff --git a/tests/rebase/iterator.c b/tests/rebase/iterator.c index acf2a92db..db57b0a83 100644 --- a/tests/rebase/iterator.c +++ b/tests/rebase/iterator.c @@ -13,7 +13,8 @@ void test_rebase_iterator__initialize(void) { repo = cl_git_sandbox_init("rebase"); cl_git_pass(git_repository_index(&_index, repo)); - cl_git_pass(git_signature_now(&signature, "Rebaser", "rebaser@rebaser.rb")); + cl_git_pass(git_signature_new(&signature, "Rebaser", + "rebaser@rebaser.rb", 1405694510, 0)); } void test_rebase_iterator__cleanup(void) @@ -46,54 +47,77 @@ static void test_operations(git_rebase *rebase, size_t expected_current) } } -void test_rebase_iterator__iterates(void) +void test_iterator(bool inmemory) { git_rebase *rebase; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; git_reference *branch_ref, *upstream_ref; git_annotated_commit *branch_head, *upstream_head; git_rebase_operation *rebase_operation; - git_oid commit_id; + git_oid commit_id, expected_id; int error; + opts.inmemory = inmemory; + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); 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)); - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); test_operations(rebase, GIT_REBASE_NO_OPERATION); - git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + if (!inmemory) { + git_rebase_free(rebase); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + } + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 0); + git_oid_fromstr(&expected_id, "776e4c48922799f903f03f5f6e51da8b01e4cce0"); + cl_assert_equal_oid(&expected_id, &commit_id); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 1); + git_oid_fromstr(&expected_id, "ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a"); + cl_assert_equal_oid(&expected_id, &commit_id); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 2); - git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + git_oid_fromstr(&expected_id, "948b12fe18b84f756223a61bece4c307787cd5d4"); + cl_assert_equal_oid(&expected_id, &commit_id); + + if (!inmemory) { + git_rebase_free(rebase); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + } cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 3); + git_oid_fromstr(&expected_id, "d9d5d59d72c9968687f9462578d79878cd80e781"); + cl_assert_equal_oid(&expected_id, &commit_id); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); test_operations(rebase, 4); + git_oid_fromstr(&expected_id, "9cf383c0a125d89e742c5dec58ed277dd07588b3"); + cl_assert_equal_oid(&expected_id, &commit_id); + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); cl_assert_equal_i(GIT_ITEROVER, error); test_operations(rebase, 4); @@ -104,3 +128,13 @@ void test_rebase_iterator__iterates(void) git_reference_free(upstream_ref); git_rebase_free(rebase); } + +void test_rebase_iterator__iterates(void) +{ + test_iterator(false); +} + +void test_rebase_iterator__iterates_inmemory(void) +{ + test_iterator(true); +} diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c index 33eadb7ed..c60113b64 100644 --- a/tests/rebase/merge.c +++ b/tests/rebase/merge.c @@ -565,3 +565,33 @@ void test_rebase_merge__custom_checkout_options(void) git_reference_free(upstream_ref); git_rebase_free(rebase); } + +void test_rebase_merge__custom_merge_options(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; + git_rebase_operation *rebase_operation; + + rebase_options.merge_options.flags |= + GIT_MERGE_FAIL_ON_CONFLICT | + GIT_MERGE_SKIP_REUC; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + 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)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); + + cl_git_fail_with(GIT_EMERGECONFLICT, git_rebase_next(&rebase_operation, rebase)); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + diff --git a/tests/refs/branches/delete.c b/tests/refs/branches/delete.c index 343ff0f50..8807db231 100644 --- a/tests/refs/branches/delete.c +++ b/tests/refs/branches/delete.c @@ -132,6 +132,8 @@ void test_refs_branches_delete__removes_reflog(void) cl_git_pass(git_branch_delete(branch)); git_reference_free(branch); + cl_assert_equal_i(false, git_reference_has_log(repo, "refs/heads/track-local")); + /* Reading a nonexistant reflog creates it, but it should be empty */ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local")); cl_assert_equal_i(0, git_reflog_entrycount(log)); diff --git a/tests/refs/create.c b/tests/refs/create.c index 192551dbd..b96d0c90a 100644 --- a/tests/refs/create.c +++ b/tests/refs/create.c @@ -18,6 +18,8 @@ void test_refs_create__initialize(void) void test_refs_create__cleanup(void) { cl_git_sandbox_cleanup(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); } void test_refs_create__symbolic(void) @@ -119,9 +121,9 @@ void test_refs_create__oid(void) git_reference_free(looked_up_ref); } -void test_refs_create__oid_unknown(void) +/* Can by default create a reference that targets at an unknown id */ +void test_refs_create__oid_unknown_succeeds_by_default(void) { - // Can not create a new OID reference which targets at an unknown id git_reference *new_reference, *looked_up_ref; git_oid id; @@ -129,6 +131,27 @@ void test_refs_create__oid_unknown(void) git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); + git_reference_free(new_reference); + + /* Ensure the reference can't be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); + git_reference_free(looked_up_ref); +} + +/* Strict object enforcement enforces valid object id */ +void test_refs_create__oid_unknown_fails_strict_mode(void) +{ + git_reference *new_reference, *looked_up_ref; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); + /* Create and write the new object id reference */ cl_git_fail(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); @@ -151,6 +174,23 @@ void test_refs_create__propagate_eexists(void) cl_assert(error == GIT_EEXISTS); } +void test_refs_create__existing_dir_propagates_edirectory(void) +{ + git_reference *new_reference, *fail_reference; + git_oid id; + const char *dir_head = "refs/heads/new-dir/new-head", + *fail_head = "refs/heads/new-dir"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&new_reference, g_repo, dir_head, &id, 1, NULL)); + cl_git_fail_with(GIT_EDIRECTORY, + git_reference_create(&fail_reference, g_repo, fail_head, &id, false, NULL)); + + git_reference_free(new_reference); +} + static void test_invalid_name(const char *name) { git_reference *new_reference; diff --git a/tests/refs/pack.c b/tests/refs/pack.c index 7dfaf6d8f..bda86f69a 100644 --- a/tests/refs/pack.c +++ b/tests/refs/pack.c @@ -36,7 +36,7 @@ void test_refs_pack__empty(void) git_buf temp_path = GIT_BUF_INIT; cl_git_pass(git_buf_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir")); - cl_git_pass(git_futils_mkdir_r(temp_path.ptr, NULL, GIT_REFS_DIR_MODE)); + cl_git_pass(git_futils_mkdir_r(temp_path.ptr, GIT_REFS_DIR_MODE)); git_buf_free(&temp_path); packall(); diff --git a/tests/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c index 56ec422c3..fdb15502c 100644 --- a/tests/refs/reflog/reflog.c +++ b/tests/refs/reflog/reflog.c @@ -125,6 +125,77 @@ void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) git_buf_free(&master_log_path); } +void test_refs_reflog_reflog__deleting_the_reference_deletes_the_reflog(void) +{ + git_reference *master; + git_buf master_log_path = GIT_BUF_INIT; + + git_buf_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_buf_joinpath(&master_log_path, git_buf_cstr(&master_log_path), "refs/heads/master"); + + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&master_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_delete(master)); + git_reference_free(master); + + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&master_log_path))); + git_buf_free(&master_log_path); +} + +void test_refs_reflog_reflog__removes_empty_reflog_dir(void) +{ + git_reference *ref; + git_buf log_path = GIT_BUF_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + + git_buf_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_buf_joinpath(&log_path, git_buf_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&log_path))); + + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + /* new ref creation should succeed since new-dir is empty */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_buf_free(&log_path); +} + +void test_refs_reflog_reflog__fails_gracefully_on_nonempty_reflog_dir(void) +{ + git_reference *ref; + git_buf log_path = GIT_BUF_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + git_reference_free(ref); + + git_buf_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_buf_joinpath(&log_path, git_buf_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&log_path))); + + /* delete the ref manually, leave the reflog */ + cl_must_pass(p_unlink("testrepo.git/refs/heads/new-dir/new-head")); + + /* new ref creation should fail since new-dir contains reflogs still */ + git_oid_fromstr(&id, current_master_tip); + cl_git_fail_with(GIT_EDIRECTORY, git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_buf_free(&log_path); +} + static void assert_has_reflog(bool expected_result, const char *name) { cl_assert_equal_i(expected_result, git_reference_has_log(g_repo, name)); @@ -154,6 +225,49 @@ void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_re git_buf_free(&subtrees_log_path); } +void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_returns_error(void) +{ + git_reflog *reflog; + const git_error *error; + const char *refname = "refs/heads/newline"; + const char *refmessage = + "Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse."; + git_reference *ref; + git_oid id; + git_buf logpath = GIT_BUF_INIT, logcontents = GIT_BUF_INIT; + char *star; + + git_oid_fromstr(&id, current_master_tip); + + /* create a new branch */ + cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage)); + + /* corrupt the branch reflog by introducing a newline inside the reflog message (we replace '*' with '\n') */ + git_buf_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); + cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath))); + cl_assert((star = strchr(git_buf_cstr(&logcontents), '*')) != NULL); + *star = '\n'; + cl_git_rewritefile(git_buf_cstr(&logpath), git_buf_cstr(&logcontents)); + + /* confirm that the file was rewritten successfully and now contains a '\n' in the expected location */ + cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath))); + cl_assert(strstr(git_buf_cstr(&logcontents), "Reflog\nmessage") != NULL); + + /* clear the error state so we can capture the error generated by git_reflog_read */ + giterr_clear(); + + cl_git_fail(git_reflog_read(&reflog, g_repo, refname)); + + error = giterr_last(); + + cl_assert(error != NULL); + cl_assert_equal_s("Unable to parse OID - contains invalid characters", error->message); + + git_reference_free(ref); + git_buf_free(&logpath); + git_buf_free(&logcontents); +} + void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) { git_reference *master, *new_master; diff --git a/tests/repo/discover.c b/tests/repo/discover.c index 7904b6496..86bd7458f 100644 --- a/tests/repo/discover.c +++ b/tests/repo/discover.c @@ -77,7 +77,7 @@ void test_repo_discover__0(void) const char *ceiling_dirs; const mode_t mode = 0777; - git_futils_mkdir_r(DISCOVER_FOLDER, NULL, mode); + git_futils_mkdir_r(DISCOVER_FOLDER, mode); append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER); ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); @@ -88,15 +88,15 @@ void test_repo_discover__0(void) git_repository_free(repo); cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0)); - cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); cl_git_pass(git_repository_discover(&sub_repository_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs)); - cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path); ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, &sub_repository_path); - cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode)); write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"); @@ -105,13 +105,13 @@ void test_repo_discover__0(void) ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode)); write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, mode)); write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, mode)); write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode)); write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"); cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs)); cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs)); diff --git a/tests/repo/init.c b/tests/repo/init.c index 525020f5a..04d4a5c5e 100644 --- a/tests/repo/init.c +++ b/tests/repo/init.c @@ -11,6 +11,8 @@ enum repo_mode { }; static git_repository *_repo = NULL; +static git_buf _global_path = GIT_BUF_INIT; +static git_buf _tmp_path = GIT_BUF_INIT; static mode_t g_umask = 0; void test_repo_init__initialize(void) @@ -22,6 +24,20 @@ void test_repo_init__initialize(void) g_umask = p_umask(022); (void)p_umask(g_umask); } + + git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + &_global_path); +} + +void test_repo_init__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + _global_path.ptr); + git_buf_free(&_global_path); + + if (_tmp_path.size > 0 && git_path_isdir(_tmp_path.ptr)) + git_futils_rmdir_r(_tmp_path.ptr, NULL, GIT_RMDIR_REMOVE_FILES); + git_buf_free(&_tmp_path); } static void cleanup_repository(void *path) @@ -99,7 +115,7 @@ void test_repo_init__bare_repo_escaping_current_workdir(void) cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL)); cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c")); - cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE)); + cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), GIT_DIR_MODE)); /* Change the current working directory */ cl_git_pass(chdir(git_buf_cstr(&path_repository))); @@ -312,7 +328,7 @@ void test_repo_init__extended_0(void) cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts)); /* make the directory first, then it should succeed */ - cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts)); cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/")); @@ -503,7 +519,8 @@ static void assert_mode_seems_okay( static const char *template_sandbox(const char *name) { - git_buf hooks_path = GIT_BUF_INIT, link_path = GIT_BUF_INIT; + git_buf hooks_path = GIT_BUF_INIT, link_path = GIT_BUF_INIT, + dotfile_path = GIT_BUF_INIT; const char *path = cl_fixture(name); cl_fixture_sandbox(name); @@ -521,19 +538,84 @@ static const char *template_sandbox(const char *name) cl_must_pass(symlink("update.sample", link_path.ptr)); #endif + /* create a file starting with a dot */ + cl_git_pass(git_buf_joinpath(&dotfile_path, hooks_path.ptr, ".dotfile")); + cl_git_mkfile(dotfile_path.ptr, "something\n"); + git_buf_free(&dotfile_path); + + git_buf_free(&dotfile_path); git_buf_free(&link_path); git_buf_free(&hooks_path); return path; } -void test_repo_init__extended_with_template(void) +static void configure_templatedir(const char *template_path) { + git_buf config_path = GIT_BUF_INIT; + git_buf config_data = GIT_BUF_INIT; + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, + GIT_CONFIG_LEVEL_GLOBAL, &_tmp_path)); + cl_git_pass(git_buf_puts(&_tmp_path, ".tmp")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, + GIT_CONFIG_LEVEL_GLOBAL, _tmp_path.ptr)); + + cl_must_pass(p_mkdir(_tmp_path.ptr, 0777)); + + cl_git_pass(git_buf_joinpath(&config_path, _tmp_path.ptr, ".gitconfig")); + + cl_git_pass(git_buf_printf(&config_data, + "[init]\n\ttemplatedir = \"%s\"\n", template_path)); + + cl_git_mkfile(config_path.ptr, config_data.ptr); + + git_buf_free(&config_path); + git_buf_free(&config_data); +} + +static void validate_templates(git_repository *repo, const char *template_path) +{ + git_buf template_description = GIT_BUF_INIT; + git_buf repo_description = GIT_BUF_INIT; git_buf expected = GIT_BUF_INIT; git_buf actual = GIT_BUF_INIT; - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; int filemode; + cl_git_pass(git_buf_joinpath(&template_description, template_path, + "description")); + cl_git_pass(git_buf_joinpath(&repo_description, git_repository_path(repo), + "description")); + + cl_git_pass(git_futils_readbuffer(&expected, template_description.ptr)); + cl_git_pass(git_futils_readbuffer(&actual, repo_description.ptr)); + + cl_assert_equal_s(expected.ptr, actual.ptr); + + filemode = cl_repo_get_bool(repo, "core.filemode"); + + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/update.sample", filemode); + + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/link.sample", filemode); + + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/.dotfile", filemode); + + git_buf_free(&expected); + git_buf_free(&actual); + git_buf_free(&repo_description); + git_buf_free(&template_description); +} + +void test_repo_init__external_templates_specified_in_options(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + cl_set_cleanup(&cleanup_repository, "templated.git"); template_sandbox("template"); @@ -547,32 +629,64 @@ void test_repo_init__extended_with_template(void) cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/")); - cl_git_pass(git_futils_readbuffer(&expected, "template/description")); - cl_git_pass(git_futils_readbuffer( - &actual, "templated.git/description")); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_buf_free(&expected); - git_buf_free(&actual); - - filemode = cl_repo_get_bool(_repo, "core.filemode"); - - assert_hooks_match( - "template", git_repository_path(_repo), - "hooks/update.sample", filemode); - - assert_hooks_match( - "template", git_repository_path(_repo), - "hooks/link.sample", filemode); - + validate_templates(_repo, "template"); cl_fixture_cleanup("template"); } +void test_repo_init__external_templates_specified_in_config(void) +{ + git_buf template_path = GIT_BUF_INIT; + + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + cl_set_cleanup(&cleanup_repository, "templated.git"); + template_sandbox("template"); + + cl_git_pass(git_buf_joinpath(&template_path, clar_sandbox_path(), + "template")); + + configure_templatedir(template_path.ptr); + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); + + validate_templates(_repo, "template"); + cl_fixture_cleanup("template"); + + git_buf_free(&template_path); +} + +void test_repo_init__external_templates_with_leading_dot(void) +{ + git_buf template_path = GIT_BUF_INIT; + + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + cl_set_cleanup(&cleanup_repository, "templated.git"); + template_sandbox("template"); + + cl_must_pass(p_rename("template", ".template_with_leading_dot")); + + cl_git_pass(git_buf_joinpath(&template_path, clar_sandbox_path(), + ".template_with_leading_dot")); + + configure_templatedir(template_path.ptr); + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); + + validate_templates(_repo, ".template_with_leading_dot"); + cl_fixture_cleanup(".template_with_leading_dot"); + + git_buf_free(&template_path); +} + void test_repo_init__extended_with_template_and_shared_mode(void) { - git_buf expected = GIT_BUF_INIT; - git_buf actual = GIT_BUF_INIT; git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; int filemode = true; const char *repo_path = NULL; @@ -592,16 +706,6 @@ void test_repo_init__extended_with_template_and_shared_mode(void) filemode = cl_repo_get_bool(_repo, "core.filemode"); - cl_git_pass(git_futils_readbuffer( - &expected, "template/description")); - cl_git_pass(git_futils_readbuffer( - &actual, "init_shared_from_tpl/.git/description")); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_buf_free(&expected); - git_buf_free(&actual); - repo_path = git_repository_path(_repo); assert_mode_seems_okay(repo_path, "hooks", GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); @@ -610,17 +714,7 @@ void test_repo_init__extended_with_template_and_shared_mode(void) assert_mode_seems_okay(repo_path, "description", GIT_FILEMODE_BLOB, false, filemode); - /* for a non-symlinked hook, it should have shared permissions now */ - assert_hooks_match( - "template", git_repository_path(_repo), - "hooks/update.sample", filemode); - - /* for a symlinked hook, the permissions still should match the - * source link, not the GIT_REPOSITORY_INIT_SHARED_GROUP value - */ - assert_hooks_match( - "template", git_repository_path(_repo), - "hooks/link.sample", filemode); + validate_templates(_repo, "template"); cl_fixture_cleanup("template"); } @@ -631,7 +725,7 @@ void test_repo_init__can_reinit_an_initialized_repository(void) cl_set_cleanup(&cleanup_repository, "extended"); - cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); cl_git_pass(git_repository_init(&_repo, "extended", false)); cl_git_pass(git_repository_init(&reinit, "extended", false)); @@ -713,7 +807,7 @@ void test_repo_init__at_filesystem_root(void) git_buf root = GIT_BUF_INIT; int root_len; - if (!cl_getenv("GITTEST_INVASIVE_FS_STRUCTURE")) + if (!cl_is_env_set("GITTEST_INVASIVE_FS_STRUCTURE")) cl_skip(); root_len = git_path_root(sandbox); diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index bb2d3a186..c18e24a4f 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -126,6 +126,7 @@ static void expect_iterator_items( void test_repo_iterator__index(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; git_index *index; g_repo = cl_git_sandbox_init("icase"); @@ -133,19 +134,19 @@ void test_repo_iterator__index(void) cl_git_pass(git_repository_index(&index, g_repo)); /* autoexpand with no tree entries for index */ - cl_git_pass(git_iterator_for_index(&i, index, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL)); expect_iterator_items(i, 20, NULL, 20, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 22, NULL, 22, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 12, NULL, 22, NULL); git_iterator_free(i); @@ -155,6 +156,7 @@ void test_repo_iterator__index(void) void test_repo_iterator__index_icase(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; git_index *index; int caps; @@ -167,32 +169,45 @@ void test_repo_iterator__index_icase(void) cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); /* autoexpand with no tree entries over range */ - cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D")); + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 7, NULL, 7, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 3, NULL, 3, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 8, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 4, NULL, 4, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 5, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 1, NULL, 4, NULL); git_iterator_free(i); @@ -200,33 +215,47 @@ void test_repo_iterator__index_icase(void) cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); /* autoexpand with no tree entries over range */ - cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D")); + i_opts.flags = 0; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 13, NULL, 13, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 5, NULL, 5, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 14, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 6, NULL, 6, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 9, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); expect_iterator_items(i, 1, NULL, 6, NULL); git_iterator_free(i); @@ -237,6 +266,7 @@ void test_repo_iterator__index_icase(void) void test_repo_iterator__tree(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; git_tree *head; g_repo = cl_git_sandbox_init("icase"); @@ -244,19 +274,21 @@ void test_repo_iterator__tree(void) cl_git_pass(git_repository_head_tree(&head, g_repo)); /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); expect_iterator_items(i, 20, NULL, 20, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 22, NULL, 22, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 12, NULL, 22, NULL); git_iterator_free(i); @@ -267,75 +299,98 @@ void test_repo_iterator__tree_icase(void) { git_iterator *i; git_tree *head; - git_iterator_flag_t flag; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("icase"); cl_git_pass(git_repository_head_tree(&head, g_repo)); - flag = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D")); + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 7, NULL, 7, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 3, NULL, 3, NULL); git_iterator_free(i); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.start = "c"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 8, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 4, NULL, 4, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 5, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 1, NULL, 4, NULL); git_iterator_free(i); - flag = GIT_ITERATOR_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 13, NULL, 13, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 5, NULL, 5, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 14, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 6, NULL, 6, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 9, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 1, NULL, 6, NULL); git_iterator_free(i); @@ -345,6 +400,7 @@ void test_repo_iterator__tree_icase(void) void test_repo_iterator__tree_more(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; git_tree *head; static const char *expect_basic[] = { "current_file", @@ -396,19 +452,21 @@ void test_repo_iterator__tree_more(void) cl_git_pass(git_repository_head_tree(&head, g_repo)); /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); expect_iterator_items(i, 12, expect_basic, 12, expect_basic); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 13, expect_trees, 13, expect_trees); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); expect_iterator_items(i, 10, expect_noauto, 13, expect_trees); git_iterator_free(i); @@ -463,6 +521,8 @@ void test_repo_iterator__tree_case_conflicts_0(void) git_tree *tree; git_oid blob_id, biga_id, littlea_id, tree_id; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const char *expect_cs[] = { "A/1.file", "A/3.file", "a/2.file", "a/4.file" }; const char *expect_ci[] = { @@ -486,25 +546,23 @@ void test_repo_iterator__tree_case_conflicts_0(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 4, expect_cs, 4, expect_cs); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 4, expect_ci, 4, expect_ci); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees); git_iterator_free(i); @@ -517,6 +575,8 @@ void test_repo_iterator__tree_case_conflicts_1(void) git_tree *tree; git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const char *expect_cs[] = { "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" }; const char *expect_ci[] = { @@ -541,25 +601,23 @@ void test_repo_iterator__tree_case_conflicts_1(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 6, expect_cs, 6, expect_cs); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 4, expect_ci, 4, expect_ci); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees); git_iterator_free(i); @@ -572,6 +630,8 @@ void test_repo_iterator__tree_case_conflicts_2(void) git_tree *tree; git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const char *expect_cs[] = { "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO", "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO", @@ -639,19 +699,18 @@ void test_repo_iterator__tree_case_conflicts_2(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 32, expect_cs, 32, expect_cs); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 17, expect_ci, 17, expect_ci); git_iterator_free(i); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees); git_iterator_free(i); @@ -661,23 +720,24 @@ void test_repo_iterator__tree_case_conflicts_2(void) void test_repo_iterator__workdir(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("icase"); /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 20, NULL, 20, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 22, NULL, 22, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 12, NULL, 22, NULL); git_iterator_free(i); } @@ -685,73 +745,97 @@ void test_repo_iterator__workdir(void) void test_repo_iterator__workdir_icase(void) { git_iterator *i; - git_iterator_flag_t flag; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("icase"); - flag = GIT_ITERATOR_DONT_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 7, NULL, 7, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 3, NULL, 3, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 8, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 4, NULL, 4, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 5, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 1, NULL, 4, NULL); git_iterator_free(i); - flag = GIT_ITERATOR_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 13, NULL, 13, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 5, NULL, 5, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 14, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 6, NULL, 6, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 9, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); expect_iterator_items(i, 1, NULL, 6, NULL); git_iterator_free(i); } @@ -764,14 +848,14 @@ static void build_workdir_tree(const char *root, int dirs, int subs) for (i = 0; i < dirs; ++i) { if (i % 2 == 0) { p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i); cl_git_mkfile(buf, buf); buf[strlen(buf) - 5] = '\0'; } else { p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); } for (j = 0; j < subs; ++j) { @@ -781,7 +865,7 @@ static void build_workdir_tree(const char *root, int dirs, int subs) case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break; case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break; } - cl_git_pass(git_futils_mkdir(sub, NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); if (j % 2 == 0) { size_t sublen = strlen(sub); @@ -796,6 +880,7 @@ static void build_workdir_tree(const char *root, int dirs, int subs) void test_repo_iterator__workdir_depth(void) { git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("icase"); @@ -804,13 +889,13 @@ void test_repo_iterator__workdir_depth(void) build_workdir_tree("icase/dir02/sUB01", 50, 0); /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); expect_iterator_items(iter, 125, NULL, 125, NULL); git_iterator_free(iter); /* auto expand with tree entries (empty dirs silently skipped) */ - cl_git_pass(git_iterator_for_workdir( - &iter, g_repo, NULL, NULL, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); expect_iterator_items(iter, 337, NULL, 337, NULL); git_iterator_free(iter); } @@ -818,6 +903,8 @@ void test_repo_iterator__workdir_depth(void) void test_repo_iterator__fs(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + static const char *expect_base[] = { "DIR01/Sub02/file", "DIR01/sub00/file", @@ -863,18 +950,17 @@ void test_repo_iterator__fs(void) build_workdir_tree("status/subdir", 2, 4); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", 0, NULL, NULL)); + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL)); expect_iterator_items(i, 8, expect_base, 8, expect_base); git_iterator_free(i); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); expect_iterator_items(i, 18, expect_trees, 18, expect_trees); git_iterator_free(i); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); git_iterator_free(i); @@ -882,20 +968,18 @@ void test_repo_iterator__fs(void) git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); expect_iterator_items(i, 8, expect_base, 8, expect_base); git_iterator_free(i); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); expect_iterator_items(i, 18, expect_trees, 18, expect_trees); git_iterator_free(i); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); git_iterator_free(i); } @@ -923,7 +1007,7 @@ void test_repo_iterator__fs2(void) g_repo = cl_git_sandbox_init("testrepo"); cl_git_pass(git_iterator_for_filesystem( - &i, "testrepo/.git/refs", 0, NULL, NULL)); + &i, "testrepo/.git/refs", NULL)); expect_iterator_items(i, 13, expect_base, 13, expect_base); git_iterator_free(i); } @@ -947,7 +1031,7 @@ void test_repo_iterator__unreadable_dir(void) cl_git_mkfile("empty_standard_repo/r/d", "final"); cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo/r", 0, NULL, NULL)); + &i, "empty_standard_repo/r", NULL)); cl_git_pass(git_iterator_advance(&e, i)); /* a */ cl_git_fail(git_iterator_advance(&e, i)); /* b */ @@ -963,6 +1047,7 @@ void test_repo_iterator__skips_fifos_and_such(void) #ifndef GIT_WIN32 git_iterator *i; const git_index_entry *e; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("empty_standard_repo"); @@ -972,9 +1057,11 @@ void test_repo_iterator__skips_fifos_and_such(void) cl_assert(!mkfifo("empty_standard_repo/fifo", 0777)); cl_assert(!access("empty_standard_repo/fifo", F_OK)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES | + GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo", GIT_ITERATOR_INCLUDE_TREES | - GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + &i, "empty_standard_repo", &i_opts)); cl_git_pass(git_iterator_advance(&e, i)); /* .git */ cl_assert(S_ISDIR(e->mode)); @@ -989,3 +1076,469 @@ void test_repo_iterator__skips_fifos_and_such(void) git_iterator_free(i); #endif } + +void test_repo_iterator__indexfilelist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + int default_icase; + int expect; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "c"; + i_opts.end = NULL; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ + expect = ((default_icase) ? 6 : 4); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + i_opts.start = NULL; + i_opts.end = "e"; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ + expect = ((default_icase) ? 5 : 6); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_repo_iterator__indexfilelist_2(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */ + expect = default_icase ? 5 : 3; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_repo_iterator__indexfilelist_3(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_repo_iterator__indexfilelist_4(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_repo_iterator__indexfilelist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + int caps; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + /* force case sensitivity */ + cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + /* force case insensitivity */ + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + cl_git_pass(git_index_set_caps(index, caps)); + git_index_free(index); + git_vector_free(&filelist); +} + +void test_repo_iterator__workdirfilelist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + bool default_icase; + int expect; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + /* In this test we DO NOT force a case on the iteratords and verify default behavior. */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "c"; + i_opts.end = NULL; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ + expect = ((default_icase) ? 6 : 4); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + i_opts.start = NULL; + i_opts.end = "e"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ + expect = ((default_icase) ? 5 : 6); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); +} + +void test_repo_iterator__workdirfilelist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); +} + +void test_repo_iterator__treefilelist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + bool default_icase; + int expect; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + /* In this test we DO NOT force a case on the iteratords and verify default behavior. */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "c"; + i_opts.end = NULL; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ + expect = ((default_icase) ? 6 : 4); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + i_opts.start = NULL; + i_opts.end = "e"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ + expect = ((default_icase) ? 5 : 6); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} + +void test_repo_iterator__treefilelist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} diff --git a/tests/repo/open.c b/tests/repo/open.c index eb459e51d..d3d087231 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -91,7 +91,7 @@ static void make_gitlink_dir(const char *dir, const char *linktext) { git_buf path = GIT_BUF_INIT; - cl_git_pass(git_futils_mkdir(dir, NULL, 0777, GIT_MKDIR_VERIFY_DIR)); + cl_git_pass(git_futils_mkdir(dir, 0777, GIT_MKDIR_VERIFY_DIR)); cl_git_pass(git_buf_joinpath(&path, dir, ".git")); cl_git_rewritefile(path.ptr, linktext); git_buf_free(&path); @@ -222,7 +222,7 @@ void test_repo_open__bad_gitlinks(void) cl_git_sandbox_init("attr"); cl_git_pass(p_mkdir("invalid", 0777)); - cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("invalid2/.git", 0777)); for (scan = bad_links; *scan != NULL; scan++) { make_gitlink_dir("alternate", *scan); diff --git a/tests/repo/reservedname.c b/tests/repo/reservedname.c index faea0cc2b..2a5b38239 100644 --- a/tests/repo/reservedname.c +++ b/tests/repo/reservedname.c @@ -106,3 +106,27 @@ void test_repo_reservedname__submodule_pointer(void) git_repository_free(sub_repo); #endif } + +/* Like the `submodule_pointer` test (above), this ensures that we do not + * follow the gitlink to the submodule's repository location and treat that + * as a reserved name. This tests at an initial submodule update, where the + * submodule repo is being created. + */ +void test_repo_reservedname__submodule_pointer_during_create(void) +{ + git_repository *repo; + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_buf url = GIT_BUF_INIT; + + repo = setup_fixture_super(); + + cl_git_pass(git_buf_joinpath(&url, clar_sandbox_path(), "sub.git")); + cl_repo_set_string(repo, "submodule.sub.url", url.ptr); + + cl_git_pass(git_submodule_lookup(&sm, repo, "sub")); + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + git_submodule_free(sm); + git_buf_free(&url); +} diff --git a/tests/repo/state.c b/tests/repo/state.c index bf2633c17..7f20eebe8 100644 --- a/tests/repo/state.c +++ b/tests/repo/state.c @@ -57,6 +57,15 @@ void test_repo_state__revert(void) assert_repo_state(GIT_REPOSITORY_STATE_NONE); } +void test_repo_state__revert_sequence(void) +{ + setup_simple_state(GIT_REVERT_HEAD_FILE); + setup_simple_state(GIT_SEQUENCER_TODO_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REVERT_SEQUENCE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + void test_repo_state__cherry_pick(void) { setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); @@ -65,6 +74,15 @@ void test_repo_state__cherry_pick(void) assert_repo_state(GIT_REPOSITORY_STATE_NONE); } +void test_repo_state__cherrypick_sequence(void) +{ + setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); + setup_simple_state(GIT_SEQUENCER_TODO_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + void test_repo_state__bisect(void) { setup_simple_state(GIT_BISECT_LOG_FILE); diff --git a/tests/reset/hard.c b/tests/reset/hard.c index 88055adda..0c0af914c 100644 --- a/tests/reset/hard.c +++ b/tests/reset/hard.c @@ -235,3 +235,55 @@ void test_reset_hard__reflog_is_correct(void) git_annotated_commit_free(annotated); } + +void test_reset_hard__switch_file_to_dir(void) +{ + git_index_entry entry = {{ 0 }}; + git_index *idx; + git_object *commit; + git_tree *tree; + git_signature *sig; + git_oid src_tree_id, tgt_tree_id; + git_oid src_id, tgt_id; + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_signature_now(&sig, "foo", "bar")); + + /* Create the old tree */ + entry.path = "README"; + cl_git_pass(git_index_add(idx, &entry)); + entry.path = "dir"; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&src_tree_id, idx, repo)); + cl_git_pass(git_index_clear(idx)); + + cl_git_pass(git_tree_lookup(&tree, repo, &src_tree_id)); + cl_git_pass(git_commit_create(&src_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); + git_tree_free(tree); + + /* Create the new tree */ + entry.path = "README"; + cl_git_pass(git_index_add(idx, &entry)); + entry.path = "dir/FILE"; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&tgt_tree_id, idx, repo)); + cl_git_pass(git_tree_lookup(&tree, repo, &tgt_tree_id)); + cl_git_pass(git_commit_create(&tgt_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); + git_tree_free(tree); + git_index_free(idx); + git_signature_free(sig); + + /* Let's go to a known state of the src commit with the file named 'dir' */ + cl_git_pass(git_object_lookup(&commit, repo, &src_id, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); + git_object_free(commit); + + /* And now we move over to the commit with the directory named 'dir' */ + cl_git_pass(git_object_lookup(&commit, repo, &tgt_id, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); + git_object_free(commit); +} diff --git a/tests/resources/blametest.git/objects/37/681a80ca21064efd5c3bf2ef41eb3d05a1428b b/tests/resources/blametest.git/objects/37/681a80ca21064efd5c3bf2ef41eb3d05a1428b new file mode 100644 index 000000000..a6ca0fb71 Binary files /dev/null and b/tests/resources/blametest.git/objects/37/681a80ca21064efd5c3bf2ef41eb3d05a1428b differ diff --git a/tests/resources/blametest.git/objects/4e/ecfea484f8005d101e547f6bfb07c99e2b114e b/tests/resources/blametest.git/objects/4e/ecfea484f8005d101e547f6bfb07c99e2b114e new file mode 100644 index 000000000..79e0ada91 Binary files /dev/null and b/tests/resources/blametest.git/objects/4e/ecfea484f8005d101e547f6bfb07c99e2b114e differ diff --git a/tests/resources/blametest.git/objects/5a/572e2e94825f54b95417eacaa089d560c5a5e9 b/tests/resources/blametest.git/objects/5a/572e2e94825f54b95417eacaa089d560c5a5e9 new file mode 100644 index 000000000..348beab16 Binary files /dev/null and b/tests/resources/blametest.git/objects/5a/572e2e94825f54b95417eacaa089d560c5a5e9 differ diff --git a/tests/resources/blametest.git/objects/66/53ff42313eb5c82806f145391b18a9699800c7 b/tests/resources/blametest.git/objects/66/53ff42313eb5c82806f145391b18a9699800c7 new file mode 100644 index 000000000..1f1140931 Binary files /dev/null and b/tests/resources/blametest.git/objects/66/53ff42313eb5c82806f145391b18a9699800c7 differ diff --git a/tests/resources/blametest.git/objects/ad/9cb4eac23df2fe5e1264287a5872ea2a1ff8b2 b/tests/resources/blametest.git/objects/ad/9cb4eac23df2fe5e1264287a5872ea2a1ff8b2 new file mode 100644 index 000000000..077e65864 Binary files /dev/null and b/tests/resources/blametest.git/objects/ad/9cb4eac23df2fe5e1264287a5872ea2a1ff8b2 differ diff --git a/tests/resources/blametest.git/objects/de/9fe35f9906e1994e083cc59c87232bf418795b b/tests/resources/blametest.git/objects/de/9fe35f9906e1994e083cc59c87232bf418795b new file mode 100644 index 000000000..11ec90d68 Binary files /dev/null and b/tests/resources/blametest.git/objects/de/9fe35f9906e1994e083cc59c87232bf418795b differ diff --git a/tests/resources/blametest.git/refs/heads/master b/tests/resources/blametest.git/refs/heads/master index b763025d8..d1bc4ca6b 100644 Binary files a/tests/resources/blametest.git/refs/heads/master and b/tests/resources/blametest.git/refs/heads/master differ diff --git a/tests/resources/diff_format_email/.gitted/index b/tests/resources/diff_format_email/.gitted/index index f73027e56..d94f87de8 100644 Binary files a/tests/resources/diff_format_email/.gitted/index and b/tests/resources/diff_format_email/.gitted/index differ diff --git a/tests/resources/diff_format_email/.gitted/objects/62/7e7e12d87e07a83fad5b6bfa25e86ead4a5270 b/tests/resources/diff_format_email/.gitted/objects/62/7e7e12d87e07a83fad5b6bfa25e86ead4a5270 new file mode 100644 index 000000000..269a5bcf4 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/62/7e7e12d87e07a83fad5b6bfa25e86ead4a5270 differ diff --git a/tests/resources/diff_format_email/.gitted/objects/73/09653445ecf038d3e3dd9ed55edb6cb541a4ba b/tests/resources/diff_format_email/.gitted/objects/73/09653445ecf038d3e3dd9ed55edb6cb541a4ba new file mode 100644 index 000000000..ba9c5fa57 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/73/09653445ecf038d3e3dd9ed55edb6cb541a4ba differ diff --git a/tests/resources/diff_format_email/.gitted/objects/d5/ff67764c82f729b13c26a09576570d884d9687 b/tests/resources/diff_format_email/.gitted/objects/d5/ff67764c82f729b13c26a09576570d884d9687 new file mode 100644 index 000000000..e838eeb25 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/d5/ff67764c82f729b13c26a09576570d884d9687 differ diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/master b/tests/resources/diff_format_email/.gitted/refs/heads/master index f0f3f932a..3bc734d47 100644 Binary files a/tests/resources/diff_format_email/.gitted/refs/heads/master and b/tests/resources/diff_format_email/.gitted/refs/heads/master differ diff --git a/tests/resources/diff_format_email/file3.txt b/tests/resources/diff_format_email/file3.txt index 9a2d780ac..730965344 100644 Binary files a/tests/resources/diff_format_email/file3.txt and b/tests/resources/diff_format_email/file3.txt differ diff --git a/tests/resources/merge-recursive/.gitted/HEAD b/tests/resources/merge-recursive/.gitted/HEAD new file mode 100644 index 000000000..d7cef11aa Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/HEAD differ diff --git a/tests/resources/merge-recursive/.gitted/config b/tests/resources/merge-recursive/.gitted/config new file mode 100644 index 000000000..6c9406b7d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/config differ diff --git a/tests/resources/merge-recursive/.gitted/index b/tests/resources/merge-recursive/.gitted/index new file mode 100644 index 000000000..571cfd02f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/index differ diff --git a/tests/resources/merge-recursive/.gitted/info/refs b/tests/resources/merge-recursive/.gitted/info/refs new file mode 100644 index 000000000..96482e6cb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/info/refs differ diff --git a/tests/resources/merge-recursive/.gitted/objects/00/6b298c5702b04c00370d0414959765b82fd722 b/tests/resources/merge-recursive/.gitted/objects/00/6b298c5702b04c00370d0414959765b82fd722 new file mode 100644 index 000000000..d3fb85fca Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/00/6b298c5702b04c00370d0414959765b82fd722 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/00/7f1ee2af8e5d99906867c4237510e1790a89b8 b/tests/resources/merge-recursive/.gitted/objects/00/7f1ee2af8e5d99906867c4237510e1790a89b8 new file mode 100644 index 000000000..d9399d71c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/00/7f1ee2af8e5d99906867c4237510e1790a89b8 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/01/6eef4a6fefd36bdcaa93ad773449ddc5c73cbb b/tests/resources/merge-recursive/.gitted/objects/01/6eef4a6fefd36bdcaa93ad773449ddc5c73cbb new file mode 100644 index 000000000..90085847c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/01/6eef4a6fefd36bdcaa93ad773449ddc5c73cbb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/05/c6a04ac101ab1a9836a95d5ec8d16b6f6304fd b/tests/resources/merge-recursive/.gitted/objects/05/c6a04ac101ab1a9836a95d5ec8d16b6f6304fd new file mode 100644 index 000000000..c6a3a3b8d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/05/c6a04ac101ab1a9836a95d5ec8d16b6f6304fd differ diff --git a/tests/resources/merge-recursive/.gitted/objects/06/db153c36829fc656e05cdf5a3bf7183f3c10aa b/tests/resources/merge-recursive/.gitted/objects/06/db153c36829fc656e05cdf5a3bf7183f3c10aa new file mode 100644 index 000000000..85887e0f5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/06/db153c36829fc656e05cdf5a3bf7183f3c10aa differ diff --git a/tests/resources/merge-recursive/.gitted/objects/07/10c3c796e0704361472ecb904413fca0107a25 b/tests/resources/merge-recursive/.gitted/objects/07/10c3c796e0704361472ecb904413fca0107a25 new file mode 100644 index 000000000..9f48594b5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/07/10c3c796e0704361472ecb904413fca0107a25 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/07/2d89dcf3a7671ac34a8e875bb72fb39bcf14d7 b/tests/resources/merge-recursive/.gitted/objects/07/2d89dcf3a7671ac34a8e875bb72fb39bcf14d7 new file mode 100644 index 000000000..69104c948 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/07/2d89dcf3a7671ac34a8e875bb72fb39bcf14d7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0b/b7ed583d7e9ad507e8b902594f5c9126ea456b b/tests/resources/merge-recursive/.gitted/objects/0b/b7ed583d7e9ad507e8b902594f5c9126ea456b new file mode 100644 index 000000000..e7b4ba10f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0b/b7ed583d7e9ad507e8b902594f5c9126ea456b differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0e/8126647ec607f0a14122cec4b15315d790c8ff b/tests/resources/merge-recursive/.gitted/objects/0e/8126647ec607f0a14122cec4b15315d790c8ff new file mode 100644 index 000000000..c99a6865c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0e/8126647ec607f0a14122cec4b15315d790c8ff differ diff --git a/tests/resources/merge-recursive/.gitted/objects/0f/a6ead2731b9d138afe38c336c9727ea05027a7 b/tests/resources/merge-recursive/.gitted/objects/0f/a6ead2731b9d138afe38c336c9727ea05027a7 new file mode 100644 index 000000000..b06362dd8 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/0f/a6ead2731b9d138afe38c336c9727ea05027a7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/12/4d4fe29d3433fdaa2f0f455d226f2c79d89cf3 b/tests/resources/merge-recursive/.gitted/objects/12/4d4fe29d3433fdaa2f0f455d226f2c79d89cf3 new file mode 100644 index 000000000..f0ea020fb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/12/4d4fe29d3433fdaa2f0f455d226f2c79d89cf3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/15/311229e70fa62653f73dde1d4deef1a8e47a11 b/tests/resources/merge-recursive/.gitted/objects/15/311229e70fa62653f73dde1d4deef1a8e47a11 new file mode 100644 index 000000000..8c21bb357 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/15/311229e70fa62653f73dde1d4deef1a8e47a11 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/15/faa0c9991f2d65686e844651faa2ff9827887b b/tests/resources/merge-recursive/.gitted/objects/15/faa0c9991f2d65686e844651faa2ff9827887b new file mode 100644 index 000000000..54cd6f26e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/15/faa0c9991f2d65686e844651faa2ff9827887b differ diff --git a/tests/resources/merge-recursive/.gitted/objects/16/895aa5e13f8907d4adab81285557d938fad342 b/tests/resources/merge-recursive/.gitted/objects/16/895aa5e13f8907d4adab81285557d938fad342 new file mode 100644 index 000000000..4d3319bc7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/16/895aa5e13f8907d4adab81285557d938fad342 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/1c/1bdb80c04233d1a9b9755913ee233987be6175 b/tests/resources/merge-recursive/.gitted/objects/1c/1bdb80c04233d1a9b9755913ee233987be6175 new file mode 100644 index 000000000..a2146496d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/1c/1bdb80c04233d1a9b9755913ee233987be6175 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/1e/8dff96faaaa24f84943d2d9601dde61cb0398a b/tests/resources/merge-recursive/.gitted/objects/1e/8dff96faaaa24f84943d2d9601dde61cb0398a new file mode 100644 index 000000000..b6185f294 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/1e/8dff96faaaa24f84943d2d9601dde61cb0398a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/21/950d5e4e4d1a871b4dfcf72ecb6b9c162c434e b/tests/resources/merge-recursive/.gitted/objects/21/950d5e4e4d1a871b4dfcf72ecb6b9c162c434e new file mode 100644 index 000000000..a87732611 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/21/950d5e4e4d1a871b4dfcf72ecb6b9c162c434e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/34/8f16ffaeb73f319a75cec5b16a0a47d2d5e27c b/tests/resources/merge-recursive/.gitted/objects/34/8f16ffaeb73f319a75cec5b16a0a47d2d5e27c new file mode 100644 index 000000000..bd1b1f69a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/34/8f16ffaeb73f319a75cec5b16a0a47d2d5e27c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/37/185b25a204309bf74817da1a607518f13ca3ed b/tests/resources/merge-recursive/.gitted/objects/37/185b25a204309bf74817da1a607518f13ca3ed new file mode 100644 index 000000000..a8cf005bc Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/37/185b25a204309bf74817da1a607518f13ca3ed differ diff --git a/tests/resources/merge-recursive/.gitted/objects/37/a5054a9f9b4628e3924c5cb8f2147c6e2a3efc b/tests/resources/merge-recursive/.gitted/objects/37/a5054a9f9b4628e3924c5cb8f2147c6e2a3efc new file mode 100644 index 000000000..4591f0e04 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/37/a5054a9f9b4628e3924c5cb8f2147c6e2a3efc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/38/55170cef875708da06ab9ad7fc6a73b531cda1 b/tests/resources/merge-recursive/.gitted/objects/38/55170cef875708da06ab9ad7fc6a73b531cda1 new file mode 100644 index 000000000..7da945e6f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/38/55170cef875708da06ab9ad7fc6a73b531cda1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3a/3f5a6ec1c968d1d2d5d20dee0d161a4351f279 b/tests/resources/merge-recursive/.gitted/objects/3a/3f5a6ec1c968d1d2d5d20dee0d161a4351f279 new file mode 100644 index 000000000..f39a1271f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3a/3f5a6ec1c968d1d2d5d20dee0d161a4351f279 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3b/919b6e8a575b4779c8243ebea3e3beb436e88f b/tests/resources/merge-recursive/.gitted/objects/3b/919b6e8a575b4779c8243ebea3e3beb436e88f new file mode 100644 index 000000000..c85731d6b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3b/919b6e8a575b4779c8243ebea3e3beb436e88f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/3f/d41804a7906db846af5e868444782e546af46a b/tests/resources/merge-recursive/.gitted/objects/3f/d41804a7906db846af5e868444782e546af46a new file mode 100644 index 000000000..4915b864f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/3f/d41804a7906db846af5e868444782e546af46a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/41/71bb8d40e9fc830d79b757dc06ec6c14548b78 b/tests/resources/merge-recursive/.gitted/objects/41/71bb8d40e9fc830d79b757dc06ec6c14548b78 new file mode 100644 index 000000000..5dc102d35 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/41/71bb8d40e9fc830d79b757dc06ec6c14548b78 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/42/1b392106e079df6d412babd5636697938269ec b/tests/resources/merge-recursive/.gitted/objects/42/1b392106e079df6d412babd5636697938269ec new file mode 100644 index 000000000..3a8324c1b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/42/1b392106e079df6d412babd5636697938269ec differ diff --git a/tests/resources/merge-recursive/.gitted/objects/42/44d13e2bbc38510320443bbb003f3967d12436 b/tests/resources/merge-recursive/.gitted/objects/42/44d13e2bbc38510320443bbb003f3967d12436 new file mode 100644 index 000000000..a19b19120 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/42/44d13e2bbc38510320443bbb003f3967d12436 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/42/cdad903aef3e7b614675e6584a8be417941911 b/tests/resources/merge-recursive/.gitted/objects/42/cdad903aef3e7b614675e6584a8be417941911 new file mode 100644 index 000000000..99b5e6d2c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/42/cdad903aef3e7b614675e6584a8be417941911 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/43/2faca0c62dc556ad71a22f23e541a46a8b0f6f b/tests/resources/merge-recursive/.gitted/objects/43/2faca0c62dc556ad71a22f23e541a46a8b0f6f new file mode 100644 index 000000000..ff18f0e35 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/43/2faca0c62dc556ad71a22f23e541a46a8b0f6f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/43/5424798e5e1b21dd4588d1c291ba4eb179a838 b/tests/resources/merge-recursive/.gitted/objects/43/5424798e5e1b21dd4588d1c291ba4eb179a838 new file mode 100644 index 000000000..58ab23917 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/43/5424798e5e1b21dd4588d1c291ba4eb179a838 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/43/6ea75c99f527e4b42fddb46abedf7726eb719d b/tests/resources/merge-recursive/.gitted/objects/43/6ea75c99f527e4b42fddb46abedf7726eb719d new file mode 100644 index 000000000..e8825d867 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/43/6ea75c99f527e4b42fddb46abedf7726eb719d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/48/3065df53c0f4a02cdc6b2910b05d388fc17ffb b/tests/resources/merge-recursive/.gitted/objects/48/3065df53c0f4a02cdc6b2910b05d388fc17ffb new file mode 100644 index 000000000..298251b3c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/48/3065df53c0f4a02cdc6b2910b05d388fc17ffb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4b/7c5650008b2e747fe1809eeb5a1dde0e80850a b/tests/resources/merge-recursive/.gitted/objects/4b/7c5650008b2e747fe1809eeb5a1dde0e80850a new file mode 100644 index 000000000..016398531 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4b/7c5650008b2e747fe1809eeb5a1dde0e80850a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4c/49317a0912ca559d2048bc329994eb7d10474f b/tests/resources/merge-recursive/.gitted/objects/4c/49317a0912ca559d2048bc329994eb7d10474f new file mode 100644 index 000000000..d7bb4d37c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4c/49317a0912ca559d2048bc329994eb7d10474f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4d/fc1be85a9d6c9898152444d32b238b4aecf8cc b/tests/resources/merge-recursive/.gitted/objects/4d/fc1be85a9d6c9898152444d32b238b4aecf8cc new file mode 100644 index 000000000..9db684d40 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4d/fc1be85a9d6c9898152444d32b238b4aecf8cc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4e/21d2d63357bde5027d1625f5ec6b430cdeb143 b/tests/resources/merge-recursive/.gitted/objects/4e/21d2d63357bde5027d1625f5ec6b430cdeb143 new file mode 100644 index 000000000..34f183dd1 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4e/21d2d63357bde5027d1625f5ec6b430cdeb143 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4e/70a6b06fc62481f80fbb74327849e7170eebff b/tests/resources/merge-recursive/.gitted/objects/4e/70a6b06fc62481f80fbb74327849e7170eebff new file mode 100644 index 000000000..1dfcec50b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4e/70a6b06fc62481f80fbb74327849e7170eebff differ diff --git a/tests/resources/merge-recursive/.gitted/objects/4f/4e85a0ab8515e34302721fbcec06fa9d9c1a9a b/tests/resources/merge-recursive/.gitted/objects/4f/4e85a0ab8515e34302721fbcec06fa9d9c1a9a new file mode 100644 index 000000000..4752ea07a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/4f/4e85a0ab8515e34302721fbcec06fa9d9c1a9a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/50/e4facaafb746cfed89287206274193c1417288 b/tests/resources/merge-recursive/.gitted/objects/50/e4facaafb746cfed89287206274193c1417288 new file mode 100644 index 000000000..b1eaee557 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/50/e4facaafb746cfed89287206274193c1417288 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/53/9bd011c4822c560c1d17cab095006b7a10f707 b/tests/resources/merge-recursive/.gitted/objects/53/9bd011c4822c560c1d17cab095006b7a10f707 new file mode 100644 index 000000000..3fa1e1f94 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/53/9bd011c4822c560c1d17cab095006b7a10f707 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/56/07a8c4601a737daadd1f470bde3142aff57026 b/tests/resources/merge-recursive/.gitted/objects/56/07a8c4601a737daadd1f470bde3142aff57026 new file mode 100644 index 000000000..bf3639d05 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/56/07a8c4601a737daadd1f470bde3142aff57026 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5a/ba269b3be41fc8db38068d3948c8af543fe609 b/tests/resources/merge-recursive/.gitted/objects/5a/ba269b3be41fc8db38068d3948c8af543fe609 new file mode 100644 index 000000000..85bc8f569 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5a/ba269b3be41fc8db38068d3948c8af543fe609 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5b/8e1e56cb99e8b99ac22eec8aebf6422ecd08c0 b/tests/resources/merge-recursive/.gitted/objects/5b/8e1e56cb99e8b99ac22eec8aebf6422ecd08c0 new file mode 100644 index 000000000..c3e6d31ca Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5b/8e1e56cb99e8b99ac22eec8aebf6422ecd08c0 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5e/8747f5200fac0f945a07daf6163ca9cb1a8da9 b/tests/resources/merge-recursive/.gitted/objects/5e/8747f5200fac0f945a07daf6163ca9cb1a8da9 new file mode 100644 index 000000000..fa1c9e5dc Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5e/8747f5200fac0f945a07daf6163ca9cb1a8da9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/5f/18576d464946eb2338daeb8b4030019961f505 b/tests/resources/merge-recursive/.gitted/objects/5f/18576d464946eb2338daeb8b4030019961f505 new file mode 100644 index 000000000..d03742b92 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/5f/18576d464946eb2338daeb8b4030019961f505 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/63/e8773becdea9c3699c95a5740be5baa8be8d69 b/tests/resources/merge-recursive/.gitted/objects/63/e8773becdea9c3699c95a5740be5baa8be8d69 new file mode 100644 index 000000000..6d5c320fe Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/63/e8773becdea9c3699c95a5740be5baa8be8d69 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/65/bea8448ca5b3104628ffbca553c54bde54b0fc b/tests/resources/merge-recursive/.gitted/objects/65/bea8448ca5b3104628ffbca553c54bde54b0fc new file mode 100644 index 000000000..031c91359 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/65/bea8448ca5b3104628ffbca553c54bde54b0fc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/66/6ffdfcf1eaa5641fa31064bf2607327e843c09 b/tests/resources/merge-recursive/.gitted/objects/66/6ffdfcf1eaa5641fa31064bf2607327e843c09 new file mode 100644 index 000000000..8d6be4216 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/66/6ffdfcf1eaa5641fa31064bf2607327e843c09 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/68/a2e1ee61a23a4728fe6b35580fbbbf729df370 b/tests/resources/merge-recursive/.gitted/objects/68/a2e1ee61a23a4728fe6b35580fbbbf729df370 new file mode 100644 index 000000000..6d7c948c9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/68/a2e1ee61a23a4728fe6b35580fbbbf729df370 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/68/af1fc7407fd9addf1701a87eb1c95c7494c598 b/tests/resources/merge-recursive/.gitted/objects/68/af1fc7407fd9addf1701a87eb1c95c7494c598 new file mode 100644 index 000000000..6aaf79fcb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/68/af1fc7407fd9addf1701a87eb1c95c7494c598 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/68/f6182f4c85d39e1309d97c7e456156dc9c0096 b/tests/resources/merge-recursive/.gitted/objects/68/f6182f4c85d39e1309d97c7e456156dc9c0096 new file mode 100644 index 000000000..ed1de3ada Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/68/f6182f4c85d39e1309d97c7e456156dc9c0096 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/6c/778edd0e4cf394f5a3df8b96db516024cc1bb8 b/tests/resources/merge-recursive/.gitted/objects/6c/778edd0e4cf394f5a3df8b96db516024cc1bb8 new file mode 100644 index 000000000..ec1db19e6 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/6c/778edd0e4cf394f5a3df8b96db516024cc1bb8 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/6e/f31d35a3f5abc1e24f4f9afa5cb2016f03fa2d b/tests/resources/merge-recursive/.gitted/objects/6e/f31d35a3f5abc1e24f4f9afa5cb2016f03fa2d new file mode 100644 index 000000000..e95a5e2db Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/6e/f31d35a3f5abc1e24f4f9afa5cb2016f03fa2d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/71/3e438567b28543235faf265c4c5b02b437c7fd b/tests/resources/merge-recursive/.gitted/objects/71/3e438567b28543235faf265c4c5b02b437c7fd new file mode 100644 index 000000000..8b1f688ca Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/71/3e438567b28543235faf265c4c5b02b437c7fd differ diff --git a/tests/resources/merge-recursive/.gitted/objects/72/3181f1bfd30e47a6d1d36a4d874e31e7a0a1a4 b/tests/resources/merge-recursive/.gitted/objects/72/3181f1bfd30e47a6d1d36a4d874e31e7a0a1a4 new file mode 100644 index 000000000..01d113eec Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/72/3181f1bfd30e47a6d1d36a4d874e31e7a0a1a4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/73/b20c8e09fa2726d69ff66969186014165da3c3 b/tests/resources/merge-recursive/.gitted/objects/73/b20c8e09fa2726d69ff66969186014165da3c3 new file mode 100644 index 000000000..6559958c5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/73/b20c8e09fa2726d69ff66969186014165da3c3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/74/4df1bdf0f7bca20deb23e5a5eb8255fc237901 b/tests/resources/merge-recursive/.gitted/objects/74/4df1bdf0f7bca20deb23e5a5eb8255fc237901 new file mode 100644 index 000000000..c05cdad8f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/74/4df1bdf0f7bca20deb23e5a5eb8255fc237901 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/75/c653822173a8e5795153ec3773dfe44bb9bb63 b/tests/resources/merge-recursive/.gitted/objects/75/c653822173a8e5795153ec3773dfe44bb9bb63 new file mode 100644 index 000000000..1495f70f4 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/75/c653822173a8e5795153ec3773dfe44bb9bb63 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/78/3d6539dde96b8873c5b5da3e79cc14cd64830b b/tests/resources/merge-recursive/.gitted/objects/78/3d6539dde96b8873c5b5da3e79cc14cd64830b new file mode 100644 index 000000000..e2f34d6bb Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/78/3d6539dde96b8873c5b5da3e79cc14cd64830b differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7a/9277e0c5ec75339f011c176d0c20e513c4de1c b/tests/resources/merge-recursive/.gitted/objects/7a/9277e0c5ec75339f011c176d0c20e513c4de1c new file mode 100644 index 000000000..9fb34f7ee Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7a/9277e0c5ec75339f011c176d0c20e513c4de1c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7c/7bf85e978f1d18c0566f702d2cb7766b9c8d4f b/tests/resources/merge-recursive/.gitted/objects/7c/7bf85e978f1d18c0566f702d2cb7766b9c8d4f new file mode 100644 index 000000000..fe8b15777 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7c/7bf85e978f1d18c0566f702d2cb7766b9c8d4f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7c/7e08f9559d9e1551b91e1cf68f1d0066109add b/tests/resources/merge-recursive/.gitted/objects/7c/7e08f9559d9e1551b91e1cf68f1d0066109add new file mode 100644 index 000000000..48d22f6a9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7c/7e08f9559d9e1551b91e1cf68f1d0066109add differ diff --git a/tests/resources/merge-recursive/.gitted/objects/7e/3056f6765b3044ab09701077dbe1eb5b0e9ad0 b/tests/resources/merge-recursive/.gitted/objects/7e/3056f6765b3044ab09701077dbe1eb5b0e9ad0 new file mode 100644 index 000000000..c4b835518 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/7e/3056f6765b3044ab09701077dbe1eb5b0e9ad0 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/81/5b5a1c80ca749d705c7aa0cb294a00cbedd340 b/tests/resources/merge-recursive/.gitted/objects/81/5b5a1c80ca749d705c7aa0cb294a00cbedd340 new file mode 100644 index 000000000..12eb0662a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/81/5b5a1c80ca749d705c7aa0cb294a00cbedd340 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/88/8588a782ad433fbf0cc526e07cfe6f4a6b60b3 b/tests/resources/merge-recursive/.gitted/objects/88/8588a782ad433fbf0cc526e07cfe6f4a6b60b3 new file mode 100644 index 000000000..44efd3315 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/88/8588a782ad433fbf0cc526e07cfe6f4a6b60b3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/88/eb3f98849f4b8d0555395f514800900a01dc8f b/tests/resources/merge-recursive/.gitted/objects/88/eb3f98849f4b8d0555395f514800900a01dc8f new file mode 100644 index 000000000..41c812f10 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/88/eb3f98849f4b8d0555395f514800900a01dc8f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/89/8d12687fb35be271c27c795a6b32c8b51da79e b/tests/resources/merge-recursive/.gitted/objects/89/8d12687fb35be271c27c795a6b32c8b51da79e new file mode 100644 index 000000000..2ce4f7f0a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/89/8d12687fb35be271c27c795a6b32c8b51da79e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/8a/bda8de114a93f2d3c5a975ee2960f31e24be58 b/tests/resources/merge-recursive/.gitted/objects/8a/bda8de114a93f2d3c5a975ee2960f31e24be58 new file mode 100644 index 000000000..a03624d81 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/8a/bda8de114a93f2d3c5a975ee2960f31e24be58 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/8f/35f30bfe09513f96cf8aa4df0834ae34e93bae b/tests/resources/merge-recursive/.gitted/objects/8f/35f30bfe09513f96cf8aa4df0834ae34e93bae new file mode 100644 index 000000000..1011a885d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/8f/35f30bfe09513f96cf8aa4df0834ae34e93bae differ diff --git a/tests/resources/merge-recursive/.gitted/objects/94/d2c01087f48213bd157222d54edfefd77c9bba b/tests/resources/merge-recursive/.gitted/objects/94/d2c01087f48213bd157222d54edfefd77c9bba new file mode 100644 index 000000000..76ffe4ea7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/94/d2c01087f48213bd157222d54edfefd77c9bba differ diff --git a/tests/resources/merge-recursive/.gitted/objects/95/78b04e2087976e382622322ba476aa40398dc7 b/tests/resources/merge-recursive/.gitted/objects/95/78b04e2087976e382622322ba476aa40398dc7 new file mode 100644 index 000000000..e3d15aac3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/95/78b04e2087976e382622322ba476aa40398dc7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/96/23368f0fc562d6d840372ae17dc4cc32d51a80 b/tests/resources/merge-recursive/.gitted/objects/96/23368f0fc562d6d840372ae17dc4cc32d51a80 new file mode 100644 index 000000000..70bf8fbb0 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/96/23368f0fc562d6d840372ae17dc4cc32d51a80 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/97/3b70322e758da87e1ce21d2195d86c5e4e9647 b/tests/resources/merge-recursive/.gitted/objects/97/3b70322e758da87e1ce21d2195d86c5e4e9647 new file mode 100644 index 000000000..a90a61cd7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/97/3b70322e758da87e1ce21d2195d86c5e4e9647 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/98/1c79eb38518d3821e73bb159dc413bb42d6614 b/tests/resources/merge-recursive/.gitted/objects/98/1c79eb38518d3821e73bb159dc413bb42d6614 new file mode 100644 index 000000000..d5787b44d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/98/1c79eb38518d3821e73bb159dc413bb42d6614 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9a/e63b4a8ce0f181b2d1d098971733a103226917 b/tests/resources/merge-recursive/.gitted/objects/9a/e63b4a8ce0f181b2d1d098971733a103226917 new file mode 100644 index 000000000..0cbd00d53 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9a/e63b4a8ce0f181b2d1d098971733a103226917 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9b/258ad4c39f40c24f66bf1faf48eb6202d59c85 b/tests/resources/merge-recursive/.gitted/objects/9b/258ad4c39f40c24f66bf1faf48eb6202d59c85 new file mode 100644 index 000000000..305e1f3e9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9b/258ad4c39f40c24f66bf1faf48eb6202d59c85 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9c/3f1c70db28c00ce74b22ba3edafe16d9cf03d4 b/tests/resources/merge-recursive/.gitted/objects/9c/3f1c70db28c00ce74b22ba3edafe16d9cf03d4 new file mode 100644 index 000000000..b0ef1af86 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9c/3f1c70db28c00ce74b22ba3edafe16d9cf03d4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/9e/12bce04446d097ae1782967a5888c2e2a0d35b b/tests/resources/merge-recursive/.gitted/objects/9e/12bce04446d097ae1782967a5888c2e2a0d35b new file mode 100644 index 000000000..0940d09c2 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/9e/12bce04446d097ae1782967a5888c2e2a0d35b differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a0/2d4fd126e0cc8fb46ee48cf38bad36d44f2dbc b/tests/resources/merge-recursive/.gitted/objects/a0/2d4fd126e0cc8fb46ee48cf38bad36d44f2dbc new file mode 100644 index 000000000..566976715 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a0/2d4fd126e0cc8fb46ee48cf38bad36d44f2dbc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a0/65d3022e99a1943177c10a53cce38bc2127042 b/tests/resources/merge-recursive/.gitted/objects/a0/65d3022e99a1943177c10a53cce38bc2127042 new file mode 100644 index 000000000..d14b7394c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a0/65d3022e99a1943177c10a53cce38bc2127042 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a2/8c21c90aa36580641b345011869d1a899a6783 b/tests/resources/merge-recursive/.gitted/objects/a2/8c21c90aa36580641b345011869d1a899a6783 new file mode 100644 index 000000000..91ffb4b88 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a2/8c21c90aa36580641b345011869d1a899a6783 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a2/fa36ffc4a565a223e225d15b18774f87d0c4f0 b/tests/resources/merge-recursive/.gitted/objects/a2/fa36ffc4a565a223e225d15b18774f87d0c4f0 new file mode 100644 index 000000000..347139464 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a2/fa36ffc4a565a223e225d15b18774f87d0c4f0 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a3/4e5a16feabbd0335a633aadb8217c9f3dba58d b/tests/resources/merge-recursive/.gitted/objects/a3/4e5a16feabbd0335a633aadb8217c9f3dba58d new file mode 100644 index 000000000..00f9c2ddd Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a3/4e5a16feabbd0335a633aadb8217c9f3dba58d differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a7/b066537e6be7109abfe4ff97b675d4e077da20 b/tests/resources/merge-recursive/.gitted/objects/a7/b066537e6be7109abfe4ff97b675d4e077da20 new file mode 100644 index 000000000..54f9b6617 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a7/b066537e6be7109abfe4ff97b675d4e077da20 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/a8/2a121ea36b115548d6dad2cd86ec27f06f7b30 b/tests/resources/merge-recursive/.gitted/objects/a8/2a121ea36b115548d6dad2cd86ec27f06f7b30 new file mode 100644 index 000000000..e740872fa Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/a8/2a121ea36b115548d6dad2cd86ec27f06f7b30 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/aa/9e263294fd2f6f6fd9ceab23ca8ce3ea2ce707 b/tests/resources/merge-recursive/.gitted/objects/aa/9e263294fd2f6f6fd9ceab23ca8ce3ea2ce707 new file mode 100644 index 000000000..0ec6cd891 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/aa/9e263294fd2f6f6fd9ceab23ca8ce3ea2ce707 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ad/1ea02c2cc4f55c1dff87b80a086206a73885eb b/tests/resources/merge-recursive/.gitted/objects/ad/1ea02c2cc4f55c1dff87b80a086206a73885eb new file mode 100644 index 000000000..99207a9dd Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ad/1ea02c2cc4f55c1dff87b80a086206a73885eb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ad/2ace9e15f66b3d1138922e6ffdc3ea3f967fa6 b/tests/resources/merge-recursive/.gitted/objects/ad/2ace9e15f66b3d1138922e6ffdc3ea3f967fa6 new file mode 100644 index 000000000..8ae3ba5a7 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ad/2ace9e15f66b3d1138922e6ffdc3ea3f967fa6 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ad/98bfa4679fb00b89207a0a11b8bbf91a3e4de9 b/tests/resources/merge-recursive/.gitted/objects/ad/98bfa4679fb00b89207a0a11b8bbf91a3e4de9 new file mode 100644 index 000000000..457f9da1f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ad/98bfa4679fb00b89207a0a11b8bbf91a3e4de9 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b2/a81ead9e722af0099fccfb478cea88eea749a2 b/tests/resources/merge-recursive/.gitted/objects/b2/a81ead9e722af0099fccfb478cea88eea749a2 new file mode 100644 index 000000000..7a8ffe58a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b2/a81ead9e722af0099fccfb478cea88eea749a2 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b4/cefb3c75770e57bb8bb44e4a50d9578009e847 b/tests/resources/merge-recursive/.gitted/objects/b4/cefb3c75770e57bb8bb44e4a50d9578009e847 new file mode 100644 index 000000000..836bb4edc Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b4/cefb3c75770e57bb8bb44e4a50d9578009e847 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/b9/1ef5ffa8612616c8e76051901caafd723f0e2c b/tests/resources/merge-recursive/.gitted/objects/b9/1ef5ffa8612616c8e76051901caafd723f0e2c new file mode 100644 index 000000000..e19652394 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/b9/1ef5ffa8612616c8e76051901caafd723f0e2c differ diff --git a/tests/resources/merge-recursive/.gitted/objects/bd/97980c22d122509cdd915fd9788d56c8d3ae20 b/tests/resources/merge-recursive/.gitted/objects/bd/97980c22d122509cdd915fd9788d56c8d3ae20 new file mode 100644 index 000000000..71295e071 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/bd/97980c22d122509cdd915fd9788d56c8d3ae20 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c0/bd078a61d2cc22c52ca5ce04abdcdc5cc1829e b/tests/resources/merge-recursive/.gitted/objects/c0/bd078a61d2cc22c52ca5ce04abdcdc5cc1829e new file mode 100644 index 000000000..3dde6c243 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c0/bd078a61d2cc22c52ca5ce04abdcdc5cc1829e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c4/83ca4bb087174af5cb51d7caa9c09fe4a28ccb b/tests/resources/merge-recursive/.gitted/objects/c4/83ca4bb087174af5cb51d7caa9c09fe4a28ccb new file mode 100644 index 000000000..643a98280 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c4/83ca4bb087174af5cb51d7caa9c09fe4a28ccb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/c4/e6cca3ec6ae0148ed231f97257df8c311e015f b/tests/resources/merge-recursive/.gitted/objects/c4/e6cca3ec6ae0148ed231f97257df8c311e015f new file mode 100644 index 000000000..2bbf28f57 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/c4/e6cca3ec6ae0148ed231f97257df8c311e015f differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ca/224bba0a8a24f1768804fe5f565b1014af7ef2 b/tests/resources/merge-recursive/.gitted/objects/ca/224bba0a8a24f1768804fe5f565b1014af7ef2 new file mode 100644 index 000000000..0dd861f2c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ca/224bba0a8a24f1768804fe5f565b1014af7ef2 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ca/49d1a8b6116ffeba22667bba265fa5261df7ab b/tests/resources/merge-recursive/.gitted/objects/ca/49d1a8b6116ffeba22667bba265fa5261df7ab new file mode 100644 index 000000000..1ea596763 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ca/49d1a8b6116ffeba22667bba265fa5261df7ab differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ca/7d316d6d9af99d2481e980d68b77e572d80fe7 b/tests/resources/merge-recursive/.gitted/objects/ca/7d316d6d9af99d2481e980d68b77e572d80fe7 new file mode 100644 index 000000000..0733fa232 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ca/7d316d6d9af99d2481e980d68b77e572d80fe7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ca/fa936d25f0b397432a27201f6b3284c47df8be b/tests/resources/merge-recursive/.gitted/objects/ca/fa936d25f0b397432a27201f6b3284c47df8be new file mode 100644 index 000000000..fb012eea3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ca/fa936d25f0b397432a27201f6b3284c47df8be differ diff --git a/tests/resources/merge-recursive/.gitted/objects/cb/49ad76147f5f9439cbd6133708b76142660660 b/tests/resources/merge-recursive/.gitted/objects/cb/49ad76147f5f9439cbd6133708b76142660660 new file mode 100644 index 000000000..849668c8b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/cb/49ad76147f5f9439cbd6133708b76142660660 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d0/dd5d9083bda65ec99aa8b9b64a5a278771b70a b/tests/resources/merge-recursive/.gitted/objects/d0/dd5d9083bda65ec99aa8b9b64a5a278771b70a new file mode 100644 index 000000000..b0d951c9e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d0/dd5d9083bda65ec99aa8b9b64a5a278771b70a differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d2/682aaf9594080ce877b5eeee110850fd6e3480 b/tests/resources/merge-recursive/.gitted/objects/d2/682aaf9594080ce877b5eeee110850fd6e3480 new file mode 100644 index 000000000..c79a3bb0f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d2/682aaf9594080ce877b5eeee110850fd6e3480 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d6/04c75019c282144bdbbf3fd3462ba74b240efc b/tests/resources/merge-recursive/.gitted/objects/d6/04c75019c282144bdbbf3fd3462ba74b240efc new file mode 100644 index 000000000..059fcfe72 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d6/04c75019c282144bdbbf3fd3462ba74b240efc differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d7/1c24b3b113fd1d1909998c5bfe33b86a65ee03 b/tests/resources/merge-recursive/.gitted/objects/d7/1c24b3b113fd1d1909998c5bfe33b86a65ee03 new file mode 100644 index 000000000..66720086c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d7/1c24b3b113fd1d1909998c5bfe33b86a65ee03 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d8/dd349b78f19a4ebe3357bacb8138f00bf5ed41 b/tests/resources/merge-recursive/.gitted/objects/d8/dd349b78f19a4ebe3357bacb8138f00bf5ed41 new file mode 100644 index 000000000..ade33f7f5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d8/dd349b78f19a4ebe3357bacb8138f00bf5ed41 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/d8/e05a90b3c2240d71a20c2502c937d9b7d22777 b/tests/resources/merge-recursive/.gitted/objects/d8/e05a90b3c2240d71a20c2502c937d9b7d22777 new file mode 100644 index 000000000..b157ba17c Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/d8/e05a90b3c2240d71a20c2502c937d9b7d22777 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/da/b7b53383a1fec46632e60a1d847ce4f9ae14f2 b/tests/resources/merge-recursive/.gitted/objects/da/b7b53383a1fec46632e60a1d847ce4f9ae14f2 new file mode 100644 index 000000000..cc4f24369 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/da/b7b53383a1fec46632e60a1d847ce4f9ae14f2 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/db/203155a789fb749aa3c14e93eea2c744a9c6c7 b/tests/resources/merge-recursive/.gitted/objects/db/203155a789fb749aa3c14e93eea2c744a9c6c7 new file mode 100644 index 000000000..e9f7fd8fd Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/db/203155a789fb749aa3c14e93eea2c744a9c6c7 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/de/a7215f259b2cced87d1bda6c72f8b4ce37a2ff b/tests/resources/merge-recursive/.gitted/objects/de/a7215f259b2cced87d1bda6c72f8b4ce37a2ff new file mode 100644 index 000000000..55c1983a8 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/de/a7215f259b2cced87d1bda6c72f8b4ce37a2ff differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e1/512550f09d980214e46e6d3f5a2b20c3d75755 b/tests/resources/merge-recursive/.gitted/objects/e1/512550f09d980214e46e6d3f5a2b20c3d75755 new file mode 100644 index 000000000..a5f506fb3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e1/512550f09d980214e46e6d3f5a2b20c3d75755 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e1/dcfc3038be54195a59817c89782b261e46cb05 b/tests/resources/merge-recursive/.gitted/objects/e1/dcfc3038be54195a59817c89782b261e46cb05 new file mode 100644 index 000000000..3668b546a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e1/dcfc3038be54195a59817c89782b261e46cb05 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e2/93bfdddb81a853bbb16b8b58e68626f30841a4 b/tests/resources/merge-recursive/.gitted/objects/e2/93bfdddb81a853bbb16b8b58e68626f30841a4 new file mode 100644 index 000000000..fab55fea6 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e2/93bfdddb81a853bbb16b8b58e68626f30841a4 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e2/c84bb33992a455b1a7a5019f0e38d883d3f475 b/tests/resources/merge-recursive/.gitted/objects/e2/c84bb33992a455b1a7a5019f0e38d883d3f475 new file mode 100644 index 000000000..8c6686706 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e2/c84bb33992a455b1a7a5019f0e38d883d3f475 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e2/d185fa827d58134cea20b9e1df893833c6560e b/tests/resources/merge-recursive/.gitted/objects/e2/d185fa827d58134cea20b9e1df893833c6560e new file mode 100644 index 000000000..fc80c0851 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e2/d185fa827d58134cea20b9e1df893833c6560e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/e5/0fbbd701458757bdfe9815f58ed717c588d1b5 b/tests/resources/merge-recursive/.gitted/objects/e5/0fbbd701458757bdfe9815f58ed717c588d1b5 new file mode 100644 index 000000000..96467c106 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/e5/0fbbd701458757bdfe9815f58ed717c588d1b5 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ef/1783444b61a8671beea4ce1f4d0202677dfbfb b/tests/resources/merge-recursive/.gitted/objects/ef/1783444b61a8671beea4ce1f4d0202677dfbfb new file mode 100644 index 000000000..67e6e8a5e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ef/1783444b61a8671beea4ce1f4d0202677dfbfb differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f1/3e1bc6ba935fce2efffa5be4c4832404034ef1 b/tests/resources/merge-recursive/.gitted/objects/f1/3e1bc6ba935fce2efffa5be4c4832404034ef1 new file mode 100644 index 000000000..e115747a2 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f1/3e1bc6ba935fce2efffa5be4c4832404034ef1 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f1/72517a8cf39e009ffff541ee52429b89e418f3 b/tests/resources/merge-recursive/.gitted/objects/f1/72517a8cf39e009ffff541ee52429b89e418f3 new file mode 100644 index 000000000..3d29a0fe8 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f1/72517a8cf39e009ffff541ee52429b89e418f3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f1/b44c04989a3a1c14b036cfadfa328d53a7bc5e b/tests/resources/merge-recursive/.gitted/objects/f1/b44c04989a3a1c14b036cfadfa328d53a7bc5e new file mode 100644 index 000000000..7cbaaeecf Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f1/b44c04989a3a1c14b036cfadfa328d53a7bc5e differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f3/5f159ff5d44dfd9f52d63dd5b659f0521ff569 b/tests/resources/merge-recursive/.gitted/objects/f3/5f159ff5d44dfd9f52d63dd5b659f0521ff569 new file mode 100644 index 000000000..5179f2ccf Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f3/5f159ff5d44dfd9f52d63dd5b659f0521ff569 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f5/1658077d85f2264fa179b4d0848268cb3475c3 b/tests/resources/merge-recursive/.gitted/objects/f5/1658077d85f2264fa179b4d0848268cb3475c3 new file mode 100644 index 000000000..3b4eb97e9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f5/1658077d85f2264fa179b4d0848268cb3475c3 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/f7/929c5a67a4bdc98247fb4b5098675723932a64 b/tests/resources/merge-recursive/.gitted/objects/f7/929c5a67a4bdc98247fb4b5098675723932a64 new file mode 100644 index 000000000..2861579e8 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/f7/929c5a67a4bdc98247fb4b5098675723932a64 differ diff --git a/tests/resources/merge-recursive/.gitted/objects/fa/567f568ed72157c0c617438d077695b99d9aac b/tests/resources/merge-recursive/.gitted/objects/fa/567f568ed72157c0c617438d077695b99d9aac new file mode 100644 index 000000000..ad5a3cf4f Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/fa/567f568ed72157c0c617438d077695b99d9aac differ diff --git a/tests/resources/merge-recursive/.gitted/objects/fd/8b5fe88cda995e70a22ed98701e65b843e05ec b/tests/resources/merge-recursive/.gitted/objects/fd/8b5fe88cda995e70a22ed98701e65b843e05ec new file mode 100644 index 000000000..b6f14634e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/fd/8b5fe88cda995e70a22ed98701e65b843e05ec differ diff --git a/tests/resources/merge-recursive/.gitted/objects/fe/f01f3104c8047d05e8572e521c454f8fd4b8db b/tests/resources/merge-recursive/.gitted/objects/fe/f01f3104c8047d05e8572e521c454f8fd4b8db new file mode 100644 index 000000000..715b6a865 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/fe/f01f3104c8047d05e8572e521c454f8fd4b8db differ diff --git a/tests/resources/merge-recursive/.gitted/objects/ff/b36e513f5fdf8a6ba850a20142676a2ac4807d b/tests/resources/merge-recursive/.gitted/objects/ff/b36e513f5fdf8a6ba850a20142676a2ac4807d new file mode 100644 index 000000000..f655d12ea Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/objects/ff/b36e513f5fdf8a6ba850a20142676a2ac4807d differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchA-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchA-1 new file mode 100644 index 000000000..b55325c3e Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchA-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchA-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchA-2 new file mode 100644 index 000000000..d35574340 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchA-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchB-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchB-1 new file mode 100644 index 000000000..d2eecb741 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchB-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchB-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchB-2 new file mode 100644 index 000000000..d5cfb2762 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchB-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchC-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchC-1 new file mode 100644 index 000000000..346b039b4 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchC-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchC-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchC-2 new file mode 100644 index 000000000..67f3153f5 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchC-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchD-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchD-1 new file mode 100644 index 000000000..fa96ccb28 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchD-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchD-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchD-2 new file mode 100644 index 000000000..8a87f9868 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchD-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchE-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-1 new file mode 100644 index 000000000..b8d011e2d Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchE-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-2 new file mode 100644 index 000000000..5e1e1acd9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchE-3 b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-3 new file mode 100644 index 000000000..eaec8d81a Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchE-3 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchF-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchF-1 new file mode 100644 index 000000000..5f2ca915b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchF-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchF-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchF-2 new file mode 100644 index 000000000..abe2ea947 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchF-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchG-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchG-1 new file mode 100644 index 000000000..af511439b Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchG-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchG-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchG-2 new file mode 100644 index 000000000..24177a247 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchG-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchH-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchH-1 new file mode 100644 index 000000000..ffe9f8cf3 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchH-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchH-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchH-2 new file mode 100644 index 000000000..84ed1a2a9 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchH-2 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchI-1 b/tests/resources/merge-recursive/.gitted/refs/heads/branchI-1 new file mode 100644 index 000000000..2d1ecd026 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchI-1 differ diff --git a/tests/resources/merge-recursive/.gitted/refs/heads/branchI-2 b/tests/resources/merge-recursive/.gitted/refs/heads/branchI-2 new file mode 100644 index 000000000..fc360bae2 Binary files /dev/null and b/tests/resources/merge-recursive/.gitted/refs/heads/branchI-2 differ diff --git a/tests/resources/merge-recursive/asparagus.txt b/tests/resources/merge-recursive/asparagus.txt new file mode 100644 index 000000000..ffb36e513 Binary files /dev/null and b/tests/resources/merge-recursive/asparagus.txt differ diff --git a/tests/resources/merge-recursive/beef.txt b/tests/resources/merge-recursive/beef.txt new file mode 100644 index 000000000..68f6182f4 Binary files /dev/null and b/tests/resources/merge-recursive/beef.txt differ diff --git a/tests/resources/merge-recursive/bouilli.txt b/tests/resources/merge-recursive/bouilli.txt new file mode 100644 index 000000000..4b7c56500 Binary files /dev/null and b/tests/resources/merge-recursive/bouilli.txt differ diff --git a/tests/resources/merge-recursive/gravy.txt b/tests/resources/merge-recursive/gravy.txt new file mode 100644 index 000000000..c4e6cca3e Binary files /dev/null and b/tests/resources/merge-recursive/gravy.txt differ diff --git a/tests/resources/merge-recursive/oyster.txt b/tests/resources/merge-recursive/oyster.txt new file mode 100644 index 000000000..7c7e08f95 Binary files /dev/null and b/tests/resources/merge-recursive/oyster.txt differ diff --git a/tests/resources/merge-recursive/veal.txt b/tests/resources/merge-recursive/veal.txt new file mode 100644 index 000000000..898d12687 Binary files /dev/null and b/tests/resources/merge-recursive/veal.txt differ diff --git a/tests/resources/nsecs/.gitted/HEAD b/tests/resources/nsecs/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/nsecs/.gitted/HEAD differ diff --git a/tests/resources/nsecs/.gitted/config b/tests/resources/nsecs/.gitted/config new file mode 100644 index 000000000..78387c50b Binary files /dev/null and b/tests/resources/nsecs/.gitted/config differ diff --git a/tests/resources/nsecs/.gitted/index b/tests/resources/nsecs/.gitted/index new file mode 100644 index 000000000..9233f1b11 Binary files /dev/null and b/tests/resources/nsecs/.gitted/index differ diff --git a/tests/resources/nsecs/.gitted/objects/03/1986a8372d1442cfe9e3b54906a9aadc524a7e b/tests/resources/nsecs/.gitted/objects/03/1986a8372d1442cfe9e3b54906a9aadc524a7e new file mode 100644 index 000000000..a813b7424 Binary files /dev/null and b/tests/resources/nsecs/.gitted/objects/03/1986a8372d1442cfe9e3b54906a9aadc524a7e differ diff --git a/tests/resources/nsecs/.gitted/objects/03/9afd91c98f82c14e425bb6796d8ca98e9c8cac b/tests/resources/nsecs/.gitted/objects/03/9afd91c98f82c14e425bb6796d8ca98e9c8cac new file mode 100644 index 000000000..74bb7d3fe Binary files /dev/null and b/tests/resources/nsecs/.gitted/objects/03/9afd91c98f82c14e425bb6796d8ca98e9c8cac differ diff --git a/tests/resources/nsecs/.gitted/objects/6d/8b18077cc99abd8dda05a6062c646406abb2d4 b/tests/resources/nsecs/.gitted/objects/6d/8b18077cc99abd8dda05a6062c646406abb2d4 new file mode 100644 index 000000000..7bf3a956c Binary files /dev/null and b/tests/resources/nsecs/.gitted/objects/6d/8b18077cc99abd8dda05a6062c646406abb2d4 differ diff --git a/tests/resources/nsecs/.gitted/objects/c5/12b6c64656b87ea8caf37a32bc5a562d797745 b/tests/resources/nsecs/.gitted/objects/c5/12b6c64656b87ea8caf37a32bc5a562d797745 new file mode 100644 index 000000000..dcf4c8ccb Binary files /dev/null and b/tests/resources/nsecs/.gitted/objects/c5/12b6c64656b87ea8caf37a32bc5a562d797745 differ diff --git a/tests/resources/nsecs/.gitted/objects/df/78d3d51c369e1d2f1eadb73464aadd931d56b4 b/tests/resources/nsecs/.gitted/objects/df/78d3d51c369e1d2f1eadb73464aadd931d56b4 new file mode 100644 index 000000000..df45d3314 Binary files /dev/null and b/tests/resources/nsecs/.gitted/objects/df/78d3d51c369e1d2f1eadb73464aadd931d56b4 differ diff --git a/tests/resources/nsecs/.gitted/refs/heads/master b/tests/resources/nsecs/.gitted/refs/heads/master new file mode 100644 index 000000000..3dda65b65 Binary files /dev/null and b/tests/resources/nsecs/.gitted/refs/heads/master differ diff --git a/tests/resources/nsecs/a.txt b/tests/resources/nsecs/a.txt new file mode 100644 index 000000000..be4c1ee68 Binary files /dev/null and b/tests/resources/nsecs/a.txt differ diff --git a/tests/resources/nsecs/b.txt b/tests/resources/nsecs/b.txt new file mode 100644 index 000000000..19a0af40f Binary files /dev/null and b/tests/resources/nsecs/b.txt differ diff --git a/tests/resources/nsecs/c.txt b/tests/resources/nsecs/c.txt new file mode 100644 index 000000000..31d776008 Binary files /dev/null and b/tests/resources/nsecs/c.txt differ diff --git a/tests/resources/redundant.git/HEAD b/tests/resources/redundant.git/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/redundant.git/HEAD differ diff --git a/tests/resources/redundant.git/config b/tests/resources/redundant.git/config new file mode 100755 index 000000000..2f8958058 Binary files /dev/null and b/tests/resources/redundant.git/config differ diff --git a/tests/resources/redundant.git/objects/info/packs b/tests/resources/redundant.git/objects/info/packs new file mode 100644 index 000000000..fbf960f10 Binary files /dev/null and b/tests/resources/redundant.git/objects/info/packs differ diff --git a/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.idx b/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.idx new file mode 100644 index 000000000..d8e099a98 Binary files /dev/null and b/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.idx differ diff --git a/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.pack b/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.pack new file mode 100644 index 000000000..02ea49f4b Binary files /dev/null and b/tests/resources/redundant.git/objects/pack/pack-3d944c0c5bcb6b16209af847052c6ff1a521529d.pack differ diff --git a/tests/resources/redundant.git/packed-refs b/tests/resources/redundant.git/packed-refs new file mode 100644 index 000000000..e8bf04d65 Binary files /dev/null and b/tests/resources/redundant.git/packed-refs differ diff --git a/tests/resources/redundant.git/refs/.gitkeep b/tests/resources/redundant.git/refs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/tests/resources/status/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/tests/resources/status/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 new file mode 100644 index 000000000..d10ca636b Binary files /dev/null and b/tests/resources/status/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 differ diff --git a/tests/resources/status/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/tests/resources/status/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 new file mode 100644 index 000000000..0b3611ae4 Binary files /dev/null and b/tests/resources/status/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 differ diff --git a/tests/resources/status/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf b/tests/resources/status/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf new file mode 100644 index 000000000..974b72dfd Binary files /dev/null and b/tests/resources/status/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf differ diff --git a/tests/resources/sub.git/HEAD b/tests/resources/sub.git/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/sub.git/HEAD differ diff --git a/tests/resources/sub.git/config b/tests/resources/sub.git/config new file mode 100644 index 000000000..78387c50b Binary files /dev/null and b/tests/resources/sub.git/config differ diff --git a/tests/resources/sub.git/index b/tests/resources/sub.git/index new file mode 100644 index 000000000..54be69e33 Binary files /dev/null and b/tests/resources/sub.git/index differ diff --git a/tests/resources/sub.git/logs/HEAD b/tests/resources/sub.git/logs/HEAD new file mode 100644 index 000000000..f636268f6 Binary files /dev/null and b/tests/resources/sub.git/logs/HEAD differ diff --git a/tests/resources/sub.git/logs/refs/heads/master b/tests/resources/sub.git/logs/refs/heads/master new file mode 100644 index 000000000..f636268f6 Binary files /dev/null and b/tests/resources/sub.git/logs/refs/heads/master differ diff --git a/tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d b/tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d new file mode 100644 index 000000000..a095b3fb8 Binary files /dev/null and b/tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d differ diff --git a/tests/resources/sub.git/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 b/tests/resources/sub.git/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 new file mode 100644 index 000000000..ef8316670 Binary files /dev/null and b/tests/resources/sub.git/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 differ diff --git a/tests/resources/sub.git/objects/94/c7d78d85c933d1d95b56bc2de01833ba8559fb b/tests/resources/sub.git/objects/94/c7d78d85c933d1d95b56bc2de01833ba8559fb new file mode 100644 index 000000000..9adc11d71 Binary files /dev/null and b/tests/resources/sub.git/objects/94/c7d78d85c933d1d95b56bc2de01833ba8559fb differ diff --git a/tests/resources/sub.git/objects/b7/a59b3f4ea13b985f8a1e0d3757d5cd3331add8 b/tests/resources/sub.git/objects/b7/a59b3f4ea13b985f8a1e0d3757d5cd3331add8 new file mode 100644 index 000000000..221b55de7 Binary files /dev/null and b/tests/resources/sub.git/objects/b7/a59b3f4ea13b985f8a1e0d3757d5cd3331add8 differ diff --git a/tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 b/tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 new file mode 100644 index 000000000..d9bb9c84d Binary files /dev/null and b/tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 differ diff --git a/tests/resources/sub.git/refs/heads/master b/tests/resources/sub.git/refs/heads/master new file mode 100644 index 000000000..0e4d6e2a7 Binary files /dev/null and b/tests/resources/sub.git/refs/heads/master differ diff --git a/tests/resources/submodule_with_path/.gitmodules b/tests/resources/submodule_with_path/.gitmodules new file mode 100644 index 000000000..ba34c47dc Binary files /dev/null and b/tests/resources/submodule_with_path/.gitmodules differ diff --git a/tests/resources/submodule_with_path/.gitted/HEAD b/tests/resources/submodule_with_path/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/HEAD differ diff --git a/tests/resources/submodule_with_path/.gitted/config b/tests/resources/submodule_with_path/.gitted/config new file mode 100644 index 000000000..78387c50b Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/config differ diff --git a/tests/resources/submodule_with_path/.gitted/index b/tests/resources/submodule_with_path/.gitted/index new file mode 100644 index 000000000..a740b4b91 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/index differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/18/372280a56a54340fa600aa91315065c6c4c693 b/tests/resources/submodule_with_path/.gitted/objects/18/372280a56a54340fa600aa91315065c6c4c693 new file mode 100644 index 000000000..d9b4313e1 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/18/372280a56a54340fa600aa91315065c6c4c693 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/36/683131578275f6a8fd1c539e0d5da0d8adff26 b/tests/resources/submodule_with_path/.gitted/objects/36/683131578275f6a8fd1c539e0d5da0d8adff26 new file mode 100644 index 000000000..366c161c1 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/36/683131578275f6a8fd1c539e0d5da0d8adff26 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/89/ca686bb21bfb75dda99a02313831a0c418f921 b/tests/resources/submodule_with_path/.gitted/objects/89/ca686bb21bfb75dda99a02313831a0c418f921 new file mode 100644 index 000000000..7c1af6645 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/89/ca686bb21bfb75dda99a02313831a0c418f921 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/b1/620ef2628d10416a84d19c783e33dc4556c9c3 b/tests/resources/submodule_with_path/.gitted/objects/b1/620ef2628d10416a84d19c783e33dc4556c9c3 new file mode 100644 index 000000000..447558259 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/b1/620ef2628d10416a84d19c783e33dc4556c9c3 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/ba/34c47dc9d3d0b1bb335b45c9d26ba1f0fc90c7 b/tests/resources/submodule_with_path/.gitted/objects/ba/34c47dc9d3d0b1bb335b45c9d26ba1f0fc90c7 new file mode 100644 index 000000000..3069f151f Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/ba/34c47dc9d3d0b1bb335b45c9d26ba1f0fc90c7 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/c8/4bf57ba2254dba216ab5c6eb1a19fe8bd0e0d6 b/tests/resources/submodule_with_path/.gitted/objects/c8/4bf57ba2254dba216ab5c6eb1a19fe8bd0e0d6 new file mode 100644 index 000000000..9f664569c Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/c8/4bf57ba2254dba216ab5c6eb1a19fe8bd0e0d6 differ diff --git a/tests/resources/submodule_with_path/.gitted/objects/d5/45fc6b40ec9e67332b6a1d2dedcbdb1bffeb6b b/tests/resources/submodule_with_path/.gitted/objects/d5/45fc6b40ec9e67332b6a1d2dedcbdb1bffeb6b new file mode 100644 index 000000000..56a844953 Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/objects/d5/45fc6b40ec9e67332b6a1d2dedcbdb1bffeb6b differ diff --git a/tests/resources/submodule_with_path/.gitted/refs/heads/master b/tests/resources/submodule_with_path/.gitted/refs/heads/master new file mode 100644 index 000000000..4b5a5a21d Binary files /dev/null and b/tests/resources/submodule_with_path/.gitted/refs/heads/master differ diff --git a/tests/resources/super/.gitted/COMMIT_EDITMSG b/tests/resources/super/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..e2d6b8987 Binary files /dev/null and b/tests/resources/super/.gitted/COMMIT_EDITMSG differ diff --git a/tests/resources/super/.gitted/HEAD b/tests/resources/super/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/super/.gitted/HEAD differ diff --git a/tests/resources/super/.gitted/config b/tests/resources/super/.gitted/config new file mode 100644 index 000000000..06a8b7790 Binary files /dev/null and b/tests/resources/super/.gitted/config differ diff --git a/tests/resources/super/.gitted/index b/tests/resources/super/.gitted/index new file mode 100644 index 000000000..cc2ffffb9 Binary files /dev/null and b/tests/resources/super/.gitted/index differ diff --git a/tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 b/tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 new file mode 100644 index 000000000..727d3a696 Binary files /dev/null and b/tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 differ diff --git a/tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 b/tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 new file mode 100644 index 000000000..7fd889d5f Binary files /dev/null and b/tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 differ diff --git a/tests/resources/super/.gitted/objects/d7/57768b570a83e80d02edcc1032db14573e5034 b/tests/resources/super/.gitted/objects/d7/57768b570a83e80d02edcc1032db14573e5034 new file mode 100644 index 000000000..f26c68c54 Binary files /dev/null and b/tests/resources/super/.gitted/objects/d7/57768b570a83e80d02edcc1032db14573e5034 differ diff --git a/tests/resources/super/.gitted/refs/heads/master b/tests/resources/super/.gitted/refs/heads/master new file mode 100644 index 000000000..663a9dcd9 Binary files /dev/null and b/tests/resources/super/.gitted/refs/heads/master differ diff --git a/tests/resources/super/gitmodules b/tests/resources/super/gitmodules new file mode 100644 index 000000000..a3d8f7f5a Binary files /dev/null and b/tests/resources/super/gitmodules differ diff --git a/tests/resources/testrepo/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf b/tests/resources/testrepo/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf new file mode 100644 index 000000000..974b72dfd Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf differ diff --git a/tests/resources/win32-forbidden/.gitted/HEAD b/tests/resources/win32-forbidden/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/HEAD differ diff --git a/tests/resources/win32-forbidden/.gitted/config b/tests/resources/win32-forbidden/.gitted/config new file mode 100644 index 000000000..6c9406b7d Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/config differ diff --git a/tests/resources/win32-forbidden/.gitted/index b/tests/resources/win32-forbidden/.gitted/index new file mode 100644 index 000000000..1202dd9f4 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/index differ diff --git a/tests/resources/win32-forbidden/.gitted/info/exclude b/tests/resources/win32-forbidden/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/info/exclude differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/10/68072702a28a82c78902cf5bf82c3864cf4356 b/tests/resources/win32-forbidden/.gitted/objects/10/68072702a28a82c78902cf5bf82c3864cf4356 new file mode 100644 index 000000000..2d3b31adc Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/10/68072702a28a82c78902cf5bf82c3864cf4356 differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 b/tests/resources/win32-forbidden/.gitted/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 new file mode 100644 index 000000000..ef8316670 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/2d/7445a749d25269f32724aa621cb70b196bcc40 b/tests/resources/win32-forbidden/.gitted/objects/2d/7445a749d25269f32724aa621cb70b196bcc40 new file mode 100644 index 000000000..baddb1fc6 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/2d/7445a749d25269f32724aa621cb70b196bcc40 differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/34/96991d72d500af36edef68bbfcccd1661d88db b/tests/resources/win32-forbidden/.gitted/objects/34/96991d72d500af36edef68bbfcccd1661d88db new file mode 100644 index 000000000..71b6172c6 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/34/96991d72d500af36edef68bbfcccd1661d88db differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/8f/45aad6f23b9509f8786c617e19c127ae76609a b/tests/resources/win32-forbidden/.gitted/objects/8f/45aad6f23b9509f8786c617e19c127ae76609a new file mode 100644 index 000000000..8bcd980c4 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/8f/45aad6f23b9509f8786c617e19c127ae76609a differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/da/623abd956bb2fd8052c708c7ed43f05d192d37 b/tests/resources/win32-forbidden/.gitted/objects/da/623abd956bb2fd8052c708c7ed43f05d192d37 new file mode 100644 index 000000000..923462306 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/da/623abd956bb2fd8052c708c7ed43f05d192d37 differ diff --git a/tests/resources/win32-forbidden/.gitted/objects/ea/c7621a652e5261ef1c1d3e7ae31b0d84fcbaba b/tests/resources/win32-forbidden/.gitted/objects/ea/c7621a652e5261ef1c1d3e7ae31b0d84fcbaba new file mode 100644 index 000000000..32b3f02ec Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/objects/ea/c7621a652e5261ef1c1d3e7ae31b0d84fcbaba differ diff --git a/tests/resources/win32-forbidden/.gitted/refs/heads/master b/tests/resources/win32-forbidden/.gitted/refs/heads/master new file mode 100644 index 000000000..47fce0fe3 Binary files /dev/null and b/tests/resources/win32-forbidden/.gitted/refs/heads/master differ diff --git a/tests/revert/workdir.c b/tests/revert/workdir.c index 9f83bd842..802819c75 100644 --- a/tests/revert/workdir.c +++ b/tests/revert/workdir.c @@ -410,7 +410,7 @@ void test_revert_workdir__rename_1_of_2(void) { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 2, "file6.txt" }, }; - opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; opts.merge_opts.rename_threshold = 50; git_oid_fromstr(&head_oid, "cef56612d71a6af8d8015691e4865f7fece905b5"); @@ -444,7 +444,7 @@ void test_revert_workdir__rename(void) { "file4.txt", "file5.txt", "" }, }; - opts.merge_opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; opts.merge_opts.rename_threshold = 50; git_oid_fromstr(&head_oid, "55568c8de5322ff9a95d72747a239cdb64a19965"); diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 7e50452c9..5ed7da4eb 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -437,3 +437,39 @@ void test_revwalk_basic__mimic_git_rev_list(void) cl_git_fail_with(git_revwalk_next(&oid, _walk), GIT_ITEROVER); } + +void test_revwalk_basic__big_timestamp(void) +{ + git_reference *head; + git_commit *tip; + git_signature *sig; + git_tree *tree; + git_oid id; + int error; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_repository_head(&head, _repo)); + cl_git_pass(git_reference_peel((git_object **) &tip, head, GIT_OBJ_COMMIT)); + + /* Commit with a far-ahead timestamp, we should be able to parse it in the revwalk */ + cl_git_pass(git_signature_new(&sig, "Joe", "joe@example.com", 2399662595, 0)); + cl_git_pass(git_commit_tree(&tree, tip)); + + cl_git_pass(git_commit_create(&id, _repo, "HEAD", sig, sig, NULL, "some message", tree, 1, + (const git_commit **)&tip)); + + cl_git_pass(git_revwalk_push_head(_walk)); + + while ((error = git_revwalk_next(&id, _walk)) == 0) { + /* nothing */ + } + + cl_assert_equal_i(GIT_ITEROVER, error); + + git_tree_free(tree); + git_commit_free(tip); + git_reference_free(head); + git_signature_free(sig); + +} diff --git a/tests/revwalk/mergebase.c b/tests/revwalk/mergebase.c index aafe97d71..ee078b3e7 100644 --- a/tests/revwalk/mergebase.c +++ b/tests/revwalk/mergebase.c @@ -492,3 +492,23 @@ void test_revwalk_mergebase__octopus_merge_branch(void) * * a */ + +void test_revwalk_mergebase__remove_redundant(void) +{ + git_repository *repo; + git_oid one, two, base; + git_oidarray result = {NULL, 0}; + + cl_git_pass(git_repository_open(&repo, cl_fixture("redundant.git"))); + + cl_git_pass(git_oid_fromstr(&one, "d89137c93ba1ee749214ff4ce52ae9137bc833f9")); + cl_git_pass(git_oid_fromstr(&two, "91f4b95df4a59504a9813ba66912562931d990e3")); + cl_git_pass(git_oid_fromstr(&base, "6cb1f2352d974e1c5a776093017e8772416ac97a")); + + cl_git_pass(git_merge_bases(&result, repo, &one, &two)); + cl_assert_equal_i(1, result.count); + cl_assert_equal_oid(&base, &result.ids[0]); + + git_oidarray_free(&result); + git_repository_free(repo); +} diff --git a/tests/revwalk/signatureparsing.c b/tests/revwalk/signatureparsing.c index 5c7d8813d..b312bad09 100644 --- a/tests/revwalk/signatureparsing.c +++ b/tests/revwalk/signatureparsing.c @@ -38,7 +38,7 @@ void test_revwalk_signatureparsing__do_not_choke_when_name_contains_angle_bracke signature = git_commit_committer(commit); cl_assert_equal_s("foo@example.com", signature->email); - cl_assert_equal_s("", signature->name); + cl_assert_equal_s("Yu V. Bin Haacked", signature->name); cl_assert_equal_i(1323847743, (int)signature->when.time); cl_assert_equal_i(60, signature->when.offset); diff --git a/tests/status/ignore.c b/tests/status/ignore.c index ba1d69a99..c318046da 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -148,7 +148,7 @@ void test_status_ignore__ignore_pattern_contains_space(void) cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt")); cl_assert(flags == GIT_STATUS_IGNORED); - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", mode)); cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!"); cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt")); @@ -206,7 +206,7 @@ void test_status_ignore__subdirectories(void) * used a rooted path for an ignore, so I changed this behavior. */ cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/test/ignore_me", NULL, 0775)); + "empty_standard_repo/test/ignore_me", 0775)); cl_git_mkfile( "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); cl_git_mkfile( @@ -230,9 +230,9 @@ static void make_test_data(const char *reponame, const char **files) g_repo = cl_git_sandbox_init(reponame); for (scan = files; *scan != NULL; ++scan) { - cl_git_pass(git_futils_mkdir( + cl_git_pass(git_futils_mkdir_relative( *scan + repolen, reponame, - 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST)); + 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST, NULL)); cl_git_mkfile(*scan, "contents"); } } @@ -612,7 +612,7 @@ void test_status_ignore__issue_1766_negated_ignores(void) g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/a", NULL, 0775)); + "empty_standard_repo/a", 0775)); cl_git_mkfile( "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); cl_git_mkfile( @@ -622,7 +622,7 @@ void test_status_ignore__issue_1766_negated_ignores(void) assert_is_ignored("a/ignoreme"); cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/b", NULL, 0775)); + "empty_standard_repo/b", 0775)); cl_git_mkfile( "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); cl_git_mkfile( @@ -1022,3 +1022,20 @@ void test_status_ignore__negate_exact_previous(void) cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, ".buildpath")); cl_assert_equal_i(1, ignored); } + +void test_status_ignore__negate_starstar(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/.gitignore", + "code/projects/**/packages/*\n" + "!code/projects/**/packages/repositories.config"); + + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/code/projects/foo/bar/packages", 0777)); + cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); + cl_assert_equal_i(0, ignored); +} diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 75c7b71b0..fc4afc6be 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -195,7 +195,7 @@ void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void) cl_git_pass(p_rename("status/subdir", "status/current_file")); cl_git_pass(p_rename("status/swap", "status/subdir")); cl_git_mkfile("status/.new_file", "dummy"); - cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); cl_git_mkfile("status/zzz_new_file", "dummy"); @@ -917,7 +917,7 @@ void test_status_worktree__long_filenames(void) // Create directory with amazingly long filename sprintf(path, "empty_standard_repo/%s", longname); - cl_git_pass(git_futils_mkdir_r(path, NULL, 0777)); + cl_git_pass(git_futils_mkdir_r(path, 0777)); sprintf(path, "empty_standard_repo/%s/foo", longname); cl_git_mkfile(path, "dummy"); @@ -1007,7 +1007,7 @@ void test_status_worktree__unreadable(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); @@ -1041,7 +1041,7 @@ void test_status_worktree__unreadable_not_included(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); @@ -1074,7 +1074,7 @@ void test_status_worktree__unreadable_as_untracked(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); diff --git a/tests/status/worktree_init.c b/tests/status/worktree_init.c index cc7e126f1..9d5cfa5a3 100644 --- a/tests/status/worktree_init.c +++ b/tests/status/worktree_init.c @@ -191,10 +191,10 @@ void test_status_worktree_init__bracket_in_filename(void) cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); cl_assert(status_flags == GIT_STATUS_WT_NEW); - cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + cl_git_fail_with(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"), GIT_ENOTFOUND); cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); git_index_free(index); git_repository_free(repo); diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index ecea694e5..148f9273e 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -333,3 +333,58 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } + +void test_submodule_lookup__renamed(void) +{ + const char *newpath = "sm_actually_changed"; + git_index *idx; + sm_lookup_data data; + + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + + /* We're replicating 'git mv sm_unchanged sm_actually_changed' in this test */ + + cl_git_pass(p_rename("submod2/sm_unchanged", "submod2/sm_actually_changed")); + + /* Change the path in .gitmodules and stage it*/ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.path", newpath)); + git_config_free(cfg); + + cl_git_pass(git_index_add_bypath(idx, ".gitmodules")); + } + + /* Change the worktree info in the submodule's config */ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.git/modules/sm_unchanged/config")); + cl_git_pass(git_config_set_string(cfg, "core.worktree", "../../../sm_actually_changed")); + git_config_free(cfg); + } + + /* Rename the entry in the index */ + { + const git_index_entry *e; + git_index_entry entry = {{ 0 }}; + + e = git_index_get_bypath(idx, "sm_unchanged", 0); + cl_assert(e); + cl_assert_equal_i(GIT_FILEMODE_COMMIT, e->mode); + + entry.path = newpath; + entry.mode = GIT_FILEMODE_COMMIT; + git_oid_cpy(&entry.id, &e->id); + + cl_git_pass(git_index_remove(idx, "sm_unchanged", 0)); + cl_git_pass(git_index_add(idx, &entry)); + cl_git_pass(git_index_write(idx)); + } + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} diff --git a/tests/submodule/status.c b/tests/submodule/status.c index 6721ee92a..10f385ce9 100644 --- a/tests/submodule/status.c +++ b/tests/submodule/status.c @@ -92,7 +92,7 @@ void test_submodule_status__ignore_none(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); status = get_submodule_status(g_repo, "sm_unchanged"); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -141,7 +141,7 @@ void test_submodule_status__ignore_untracked(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -185,7 +185,7 @@ void test_submodule_status__ignore_dirty(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -229,7 +229,7 @@ void test_submodule_status__ignore_all(void) cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); @@ -264,6 +264,7 @@ static int confirm_submodule_status( void test_submodule_status__iterator(void) { git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; size_t i; static const char *expected[] = { @@ -308,9 +309,10 @@ void test_submodule_status__iterator(void) git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_index *index; + iter_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, - GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, &iter_opts)); for (i = 0; !git_iterator_advance(&entry, iter); ++i) cl_assert_equal_s(expected[i], entry->path); @@ -336,7 +338,7 @@ void test_submodule_status__untracked_dirs_containing_ignored_files(void) "submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n"); cl_git_pass( - git_futils_mkdir("sm_unchanged/directory", "submod2", 0755, 0)); + git_futils_mkdir_relative("sm_unchanged/directory", "submod2", 0755, 0, NULL)); cl_git_mkfile( "submod2/sm_unchanged/directory/i_am.ignored", "ignore this file, please\n"); diff --git a/tests/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c index 1dc687231..4ff4b4da7 100644 --- a/tests/submodule/submodule_helpers.c +++ b/tests/submodule/submodule_helpers.c @@ -126,6 +126,22 @@ git_repository *setup_fixture_submod2(void) return repo; } +git_repository *setup_fixture_super(void) +{ + git_repository *repo = cl_git_sandbox_init("super"); + + cl_fixture_sandbox("sub.git"); + p_mkdir("super/sub", 0777); + + rewrite_gitmodules(git_repository_workdir(repo)); + + cl_set_cleanup(cleanup_fixture_submodules, "sub.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + git_repository *setup_fixture_submodule_simple(void) { git_repository *repo = cl_git_sandbox_init("submodule_simple"); @@ -140,6 +156,21 @@ git_repository *setup_fixture_submodule_simple(void) return repo; } +git_repository *setup_fixture_submodule_with_path(void) +{ + git_repository *repo = cl_git_sandbox_init("submodule_with_path"); + + cl_fixture_sandbox("testrepo.git"); + p_mkdir("submodule_with_path/lib", 0777); + p_mkdir("submodule_with_path/lib/testrepo", 0777); + + cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + void assert__submodule_exists( git_repository *repo, const char *name, const char *msg, const char *file, int line) diff --git a/tests/submodule/submodule_helpers.h b/tests/submodule/submodule_helpers.h index 1493f245f..42b14a7bc 100644 --- a/tests/submodule/submodule_helpers.h +++ b/tests/submodule/submodule_helpers.h @@ -4,6 +4,8 @@ extern void rewrite_gitmodules(const char *workdir); extern git_repository *setup_fixture_submodules(void); extern git_repository *setup_fixture_submod2(void); extern git_repository *setup_fixture_submodule_simple(void); +extern git_repository *setup_fixture_super(void); +extern git_repository *setup_fixture_submodule_with_path(void); extern unsigned int get_submodule_status(git_repository *, const char *); diff --git a/tests/submodule/update.c b/tests/submodule/update.c index 40d24d0a7..cbd519d81 100644 --- a/tests/submodule/update.c +++ b/tests/submodule/update.c @@ -131,6 +131,53 @@ void test_submodule_update__update_submodule(void) git_submodule_free(sm); } +void test_submodule_update__update_submodule_with_path(void) +{ + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + struct update_submodule_cb_payload update_payload = { 0 }; + + g_repo = setup_fixture_submodule_with_path(); + + update_options.checkout_opts.progress_cb = checkout_progress_cb; + update_options.checkout_opts.progress_payload = &update_payload; + + update_options.fetch_opts.callbacks.update_tips = update_tips; + update_options.fetch_opts.callbacks.payload = &update_payload; + + /* get the submodule */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify the initial state of the submodule */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + /* initialize and update the submodule */ + cl_git_pass(git_submodule_init(sm, 0)); + cl_git_pass(git_submodule_update(sm, 0, &update_options)); + + /* verify state */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* verify that the expected callbacks have been called. */ + cl_assert_equal_i(1, update_payload.checkout_progress_called); + cl_assert_equal_i(1, update_payload.update_tips_called); + + git_submodule_free(sm); +} + void test_submodule_update__update_and_init_submodule(void) { git_submodule *sm; @@ -390,3 +437,4 @@ void test_submodule_update__can_force_update(void) git_object_free(branch_commit); git_reference_free(branch_reference); } + diff --git a/tests/threads/iterator.c b/tests/threads/iterator.c index 8a2d79c2e..6b86cf1a0 100644 --- a/tests/threads/iterator.c +++ b/tests/threads/iterator.c @@ -13,10 +13,13 @@ static void *run_workdir_iterator(void *arg) { int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; + iter_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_workdir( - &iter, _repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + &iter, _repo, NULL, NULL, &iter_opts)); while (!error) { if (entry && entry->mode == GIT_FILEMODE_TREE) { diff --git a/tests/trace/windows/stacktrace.c b/tests/trace/windows/stacktrace.c new file mode 100644 index 000000000..c00c1b774 --- /dev/null +++ b/tests/trace/windows/stacktrace.c @@ -0,0 +1,151 @@ +#include "clar_libgit2.h" +#include "win32/w32_stack.h" + +#if defined(GIT_MSVC_CRTDBG) +static void a(void) +{ + char buf[10000]; + + cl_assert(git_win32__stack(buf, sizeof(buf), 0, NULL, NULL) == 0); + +#if 0 + fprintf(stderr, "Stacktrace from [%s:%d]:\n%s\n", __FILE__, __LINE__, buf); +#endif +} + +static void b(void) +{ + a(); +} + +static void c(void) +{ + b(); +} +#endif + +void test_trace_windows_stacktrace__basic(void) +{ +#if defined(GIT_MSVC_CRTDBG) + c(); +#endif +} + + +void test_trace_windows_stacktrace__leaks(void) +{ +#if defined(GIT_MSVC_CRTDBG) + void * p1; + void * p2; + void * p3; + void * p4; + int before, after; + int leaks; + int error; + + /* remember outstanding leaks due to set setup + * and set mark/checkpoint. + */ + before = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL | + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, + NULL); + + p1 = git__malloc(5); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p1"); + cl_assert((leaks == 1)); + + p2 = git__malloc(5); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p1,p2"); + cl_assert((leaks == 2)); + + p3 = git__malloc(5); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p1,p2,p3"); + cl_assert((leaks == 3)); + + git__free(p2); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p1,p3"); + cl_assert((leaks == 2)); + + /* move the mark. only new leaks should appear afterwards */ + error = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, + NULL); + cl_assert((error == 0)); + + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "not_p1,not_p3"); + cl_assert((leaks == 0)); + + p4 = git__malloc(5); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p4,not_p1,not_p3"); + cl_assert((leaks == 1)); + + git__free(p1); + git__free(p3); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "p4"); + cl_assert((leaks == 1)); + + git__free(p4); + leaks = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + "end"); + cl_assert((leaks == 0)); + + /* confirm current absolute leaks count matches beginning value. */ + after = git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__QUIET | + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_TOTAL, + "total"); + cl_assert((before == after)); +#endif +} + +#if defined(GIT_MSVC_CRTDBG) +static void aux_cb_alloc__1(unsigned int *aux_id) +{ + static unsigned int aux_counter = 0; + + *aux_id = aux_counter++; +} + +static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len) +{ + p_snprintf(aux_msg, aux_msg_len, "\tQQ%08x\n", aux_id); +} + +#endif + +void test_trace_windows_stacktrace__aux1(void) +{ +#if defined(GIT_MSVC_CRTDBG) + git_win32__stack__set_aux_cb(aux_cb_alloc__1, aux_cb_lookup__1); + c(); + c(); + c(); + c(); + git_win32__stack__set_aux_cb(NULL, NULL); +#endif +} diff --git a/tests/win32/forbidden.c b/tests/win32/forbidden.c new file mode 100644 index 000000000..e02f41179 --- /dev/null +++ b/tests/win32/forbidden.c @@ -0,0 +1,183 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "buffer.h" +#include "submodule.h" + +static const char *repo_name = "win32-forbidden"; +static git_repository *repo; + +void test_win32_forbidden__initialize(void) +{ + repo = cl_git_sandbox_init(repo_name); +} + +void test_win32_forbidden__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_win32_forbidden__can_open_index(void) +{ + git_index *index; + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_i(7, git_index_entrycount(index)); + + /* ensure we can even write the unmodified index */ + cl_git_pass(git_index_write(index)); + + git_index_free(index); +} + +void test_win32_forbidden__can_add_forbidden_filename_with_entry(void) +{ + git_index *index; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_index(&index, repo)); + + entry.path = "aux"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); + + cl_git_pass(git_index_add(index, &entry)); + + git_index_free(index); +} + +void test_win32_forbidden__cannot_add_dot_git_even_with_entry(void) +{ + git_index *index; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_index(&index, repo)); + + entry.path = "foo/.git"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); + + cl_git_fail(git_index_add(index, &entry)); + + git_index_free(index); +} + +void test_win32_forbidden__cannot_add_forbidden_filename_from_filesystem(void) +{ + git_index *index; + + /* since our function calls are very low-level, we can create `aux.`, + * but we should not be able to add it to the index + */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_write2file("win32-forbidden/aux.", "foo\n", 4, O_RDWR | O_CREAT, 0666); + +#ifdef GIT_WIN32 + cl_git_fail(git_index_add_bypath(index, "aux.")); +#else + cl_git_pass(git_index_add_bypath(index, "aux.")); +#endif + + cl_must_pass(p_unlink("win32-forbidden/aux.")); + git_index_free(index); +} + +static int dummy_submodule_cb( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(sm); + GIT_UNUSED(name); + GIT_UNUSED(payload); + return 0; +} + +void test_win32_forbidden__can_diff_tree_to_index(void) +{ + git_diff *diff; + git_tree *tree; + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, NULL, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + git_tree_free(tree); +} + +void test_win32_forbidden__can_diff_tree_to_tree(void) +{ + git_diff *diff; + git_tree *tree; + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + git_tree_free(tree); +} + +void test_win32_forbidden__can_diff_index_to_workdir(void) +{ + git_index *index; + git_diff *diff; + const git_diff_delta *delta; + git_tree *tree; + size_t i; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + } + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +} + +void test_win32_forbidden__checking_out_forbidden_index_fails(void) +{ +#ifdef GIT_WIN32 + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_diff *diff; + const git_diff_delta *delta; + git_tree *tree; + size_t num_deltas, i; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_fail(git_checkout_index(repo, index, &opts)); + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); + + num_deltas = git_diff_num_deltas(diff); + + cl_assert(num_deltas > 0); + + for (i = 0; i < num_deltas; i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + } + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +#endif +} + +void test_win32_forbidden__can_query_submodules(void) +{ + cl_git_pass(git_submodule_foreach(repo, dummy_submodule_cb, NULL)); +} + +void test_win32_forbidden__can_blame_file(void) +{ + git_blame *blame; + + cl_git_pass(git_blame_file(&blame, repo, "aux", NULL)); + git_blame_free(blame); +} diff --git a/tests/win32/longpath.c b/tests/win32/longpath.c new file mode 100644 index 000000000..5a36875ed --- /dev/null +++ b/tests/win32/longpath.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "buffer.h" +#include "fileops.h" + +static git_buf path = GIT_BUF_INIT; + +void test_win32_longpath__initialize(void) +{ +#ifdef GIT_WIN32 + const char *base = clar_sandbox_path(); + size_t base_len = strlen(base); + size_t remain = MAX_PATH - base_len; + size_t i; + + git_buf_clear(&path); + git_buf_puts(&path, base); + git_buf_putc(&path, '/'); + + cl_assert(remain < (MAX_PATH - 5)); + + for (i = 0; i < (remain - 5); i++) + git_buf_putc(&path, 'a'); +#endif +} + +void test_win32_longpath__cleanup(void) +{ + git_buf_free(&path); +} + +#ifdef GIT_WIN32 +void assert_name_too_long(void) +{ + const git_error *err; + size_t expected_len, actual_len; + char *expected_msg; + + err = giterr_last(); + actual_len = strlen(err->message); + + expected_msg = git_win32_get_error_message(ERROR_FILENAME_EXCED_RANGE); + expected_len = strlen(expected_msg); + + /* check the suffix */ + cl_assert_equal_s(expected_msg, err->message + (actual_len - expected_len)); + + git__free(expected_msg); +} +#endif + +void test_win32_longpath__errmsg_on_checkout(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + + cl_git_fail(git_clone(&repo, cl_fixture("testrepo.git"), path.ptr, NULL)); + assert_name_too_long(); +#endif +}