diff --git a/.gitignore b/.gitignore index bba9d5d5c..d4e0454fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -/tests-clar/clar.suite -/tests-clar/clar.suite.rule -/tests-clar/.clarcache +/tests/clar.suite +/tests/clar.suite.rule +/tests/.clarcache /apidocs /trash-*.exe /libgit2.pc diff --git a/.mailmap b/.mailmap index 3f5a087ea..c656f64c7 100644 --- a/.mailmap +++ b/.mailmap @@ -16,3 +16,6 @@ Xavier L. Sascha Cunz Authmillenon Authmillenon +Edward Thomson +J. David Ibáñez +Russell Belfer diff --git a/.travis.yml b/.travis.yml index 0d5746f2e..151060fb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ # Travis-CI Build for libgit2 # see travis-ci.org for details -# As CMake is not officially supported we use erlang VMs language: c compiler: @@ -18,26 +17,18 @@ matrix: - compiler: i586-mingw32msvc-gcc env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" -# Make sure CMake is installed install: - - sudo apt-get update >/dev/null - - sudo apt-get -q install cmake valgrind + - sudo apt-get -qq update + - sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server -# Run the Build script +# Run the Build script and tests script: - - mkdir _temp - - git init --bare _temp/test.git - - git daemon --listen=localhost --export-all --enable=receive-pack --base-path=_temp _temp 2>/dev/null & - - export GITTEST_REMOTE_URL="git://localhost/test.git" - - mkdir _build - - cd _build - - cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS - - cmake --build . --target install - - ctest -V . + - script/cibuild.sh # Run Tests after_success: - - valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar -ionline + - sudo apt-get -qq install valgrind + - valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline # Only watch the development branch branches: diff --git a/AUTHORS b/AUTHORS index 587da249d..f3a03ee74 100644 --- a/AUTHORS +++ b/AUTHORS @@ -68,5 +68,6 @@ Sven Strickroth Tim Branyen Tim Clem Tim Harder +Torsten Bögershausen Trent Mick Vicent Marti diff --git a/CMakeLists.txt b/CMakeLists.txt index 34d56b3fa..ac1032acf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,25 +27,44 @@ OPTION( BUILD_EXAMPLES "Build library usage example apps" OFF ) OPTION( TAGS "Generate tags" OFF ) OPTION( PROFILE "Generate profiling information" OFF ) OPTION( ENABLE_TRACE "Enables tracing support" OFF ) -OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) +OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF ) + +OPTION( ANDROID "Build for android NDK" OFF ) + +OPTION( USE_ICONV "Link with and use iconv library" OFF ) +OPTION( USE_SSH "Link with libssh to enable SSH support" ON ) + +IF(APPLE) + SET( USE_ICONV ON ) +ENDIF() IF(MSVC) - # This option is only availalbe when building with MSVC. By default, - # libgit2 is build using the stdcall calling convention, as that's what - # the CLR expects by default and how the Windows API is built. + # This option is only available when building with MSVC. By default, libgit2 + # is build using the cdecl calling convention, which is useful if you're + # writing C. However, the CLR and Win32 API both expect stdcall. # - # If you are writing a C or C++ program and want to link to libgit2, you - # have to either: - # - Add /Gz to the compiler options of _your_ program / library. - # - Turn this off by invoking CMake with the "-DSTDCALL=Off" argument. - # - OPTION( STDCALL "Build libgit2 with the __stdcall convention" ON ) + # If you are writing a CLR program and want to link to libgit2, you'll want + # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument. + OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF ) # This option must match the settings used in your program, in particular if you # are linking statically OPTION( STATIC_CRT "Link the static CRT libraries" ON ) + + # By default, libgit2 is built with WinHTTP. To use the built-in + # HTTP transport, invoke CMake with the "-DWINHTTP=OFF" argument. + OPTION( WINHTTP "Use Win32 WinHTTP routines" ON ) ENDIF() +# This variable will contain the libraries we need to put into +# libgit2.pc's Requires.private. That is, what we're linking to or +# what someone who's statically linking us needs to link to. +SET(LIBGIT2_PC_REQUIRES "") +# This will be set later if we use the system's http-parser library or +# use iconv (OSX) and will be written to the Libs.private field in the +# pc file. +SET(LIBGIT2_PC_LIBS "") + # Installation paths # SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.") @@ -57,7 +76,18 @@ FUNCTION(TARGET_OS_LIBRARIES target) TARGET_LINK_LIBRARIES(${target} ws2_32) ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") TARGET_LINK_LIBRARIES(${target} socket nsl) - ENDIF () + SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lsocket -lnsl" PARENT_SCOPE) + ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Linux") + TARGET_LINK_LIBRARIES(${target} rt) + SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lrt" PARENT_SCOPE) + ENDIF() + + IF(USE_ICONV) + TARGET_LINK_LIBRARIES(${target} iconv) + ADD_DEFINITIONS(-DGIT_USE_ICONV) + SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -liconv" PARENT_SCOPE) + ENDIF() + IF(THREADSAFE) TARGET_LINK_LIBRARIES(${target} ${CMAKE_THREAD_LIBS_INIT}) ENDIF() @@ -67,7 +97,7 @@ ENDFUNCTION() # 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-clar" for the git2.dll +# empty virtual folders "tests" for the git2.dll FUNCTION(MSVC_SPLIT_SOURCES target) IF(MSVC_IDE) GET_TARGET_PROPERTY(sources ${target} SOURCES) @@ -96,8 +126,10 @@ SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${ # Find required dependencies INCLUDE_DIRECTORIES(src include) -IF (WIN32 AND NOT MINGW) +IF (WIN32 AND WINHTTP AND NOT MINGW) ADD_DEFINITIONS(-DGIT_WINHTTP) + INCLUDE_DIRECTORIES(deps/http-parser) + FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h) ELSE () IF (NOT AMIGA) FIND_PACKAGE(OpenSSL) @@ -107,10 +139,11 @@ ELSE () IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS}) LINK_LIBRARIES(${HTTP_PARSER_LIBRARIES}) + SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lhttp_parser") ELSE() MESSAGE("http-parser was not found or is too old; using bundled 3rd-party sources.") INCLUDE_DIRECTORIES(deps/http-parser) - FILE(GLOB SRC_HTTP deps/http-parser/*.c) + FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h) ENDIF() ENDIF() @@ -120,6 +153,7 @@ IF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin") FILE(GLOB SRC_SHA1 src/hash/hash_win32.c) ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin") ADD_DEFINITIONS(-DOPENSSL_SHA1) + SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} openssl") ELSE() FILE(GLOB SRC_SHA1 src/hash/hash_generic.c) ENDIF() @@ -130,7 +164,7 @@ IF (ENABLE_TRACE STREQUAL "ON") ENDIF() # Include POSIX regex when it is required -IF(WIN32 OR AMIGA) +IF(WIN32 OR AMIGA OR ANDROID) INCLUDE_DIRECTORIES(deps/regex) SET(SRC_REGEX deps/regex/regex.c) ENDIF() @@ -142,24 +176,31 @@ FIND_PACKAGE(ZLIB QUIET) IF (ZLIB_FOUND) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) LINK_LIBRARIES(${ZLIB_LIBRARIES}) + IF(APPLE) + SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lz") + ELSE() + SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} zlib") + ENDIF() # Fake the message CMake would have shown MESSAGE("-- Found zlib: ${ZLIB_LIBRARY}") ELSE() MESSAGE( "zlib was not found; using bundled 3rd-party sources." ) INCLUDE_DIRECTORIES(deps/zlib) ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) - FILE(GLOB SRC_ZLIB deps/zlib/*.c) + FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h) ENDIF() -IF(NOT LIBSSH2_LIBRARY) +IF (USE_SSH AND NOT MINGW) FIND_PACKAGE(LIBSSH2 QUIET) ENDIF() IF (LIBSSH2_FOUND) ADD_DEFINITIONS(-DGIT_SSH) INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIR}) + SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} libssh2") SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES}) ENDIF() + # Platform specific compilation flags IF (MSVC) @@ -285,19 +326,19 @@ ENDIF() ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) # Collect sourcefiles -FILE(GLOB SRC_H include/git2/*.h) +FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501) - FILE(GLOB SRC_OS src/win32/*.c) + FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h) ELSEIF (AMIGA) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R) - FILE(GLOB SRC_OS src/amiga/*.c) + FILE(GLOB SRC_OS src/amiga/*.c src/amiga/*.h) ELSE() - FILE(GLOB SRC_OS src/unix/*.c) + FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h) ENDIF() -FILE(GLOB SRC_GIT2 src/*.c src/transports/*.c src/xdiff/*.c) +FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h) # Determine architecture of the machine IF (CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -309,7 +350,7 @@ ELSE() ENDIF() # Compile and link libgit2 -ADD_LIBRARY(git2 ${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_SHA1} ${WIN_RC}) TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES}) TARGET_OS_LIBRARIES(git2) @@ -352,19 +393,19 @@ INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) IF (BUILD_CLAR) FIND_PACKAGE(PythonInterp REQUIRED) - SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar/resources/") - SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar") - SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar/resources" CACHE PATH "Path to test resources.") + SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/") + SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests") + SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.") ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") INCLUDE_DIRECTORIES(${CLAR_PATH}) - FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c) + FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c") ADD_CUSTOM_COMMAND( OUTPUT ${CLAR_PATH}/clar.suite - COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline . + COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress . DEPENDS ${SRC_TEST} WORKING_DIRECTORY ${CLAR_PATH} ) @@ -373,7 +414,7 @@ IF (BUILD_CLAR) ${CLAR_PATH}/clar.c PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) - ADD_EXECUTABLE(libgit2_clar ${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_SHA1}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES}) @@ -409,23 +450,5 @@ IF (TAGS) ENDIF () IF (BUILD_EXAMPLES) - FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c) - ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC}) - IF(WIN32) - TARGET_LINK_LIBRARIES(cgit2 git2) - ELSE() - TARGET_LINK_LIBRARIES(cgit2 git2 pthread) - ENDIF() - - ADD_EXECUTABLE(git-diff examples/diff.c) - TARGET_LINK_LIBRARIES(git-diff git2) - - ADD_EXECUTABLE(git-general examples/general.c) - TARGET_LINK_LIBRARIES(git-general git2) - - ADD_EXECUTABLE(git-showindex examples/showindex.c) - TARGET_LINK_LIBRARIES(git-showindex git2) - - ADD_EXECUTABLE(git-rev-list examples/rev-list.c) - TARGET_LINK_LIBRARIES(git-rev-list git2) + ADD_SUBDIRECTORY(examples) ENDIF () diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28ef27f42..807cd5320 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,12 @@ We're making it easy to do interesting things with git, and we'd love to have your help. +## Licensing + +By contributing to libgit2, you agree to release your contribution under the terms of the license. +For code under `examples`, this is governed by the [CC0 Public Domain Dedication](examples/COPYING). +All other code is released under the [GPL v2 with linking exception](COPYING). + ## Discussion & Chat We hang out in the #libgit2 channel on irc.freenode.net. @@ -48,6 +54,12 @@ Please include a nice description of your changes with your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change merged in. +If you are working on a particular area then feel free to submit a PR that +highlights your work in progress (and flag in the PR title that it's not +ready to merge). This will help in getting visibility for your fix, allow +others to comment early on the changes and also let others know that you +are currently working on something. + ## Porting Code From Other Open-Source Projects `libgit2` is licensed under the terms of the GPL v2 with a linking @@ -57,14 +69,17 @@ The most common case is porting code from core Git. Git is a pure GPL project, which means that in order to port code to this project, we need the explicit permission of the author. Check the [`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors) -file for authors who have already consented; feel free to add someone if -you've obtained their consent. +file for authors who have already consented. Other licenses have other requirements; check the license of the library you're porting code *from* to see what you need to do. As a general rule, MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0 license typically doesn't work due to GPL incompatibility. +If you are pulling in code from core Git, another project or code you've pulled from +a forum / Stack Overflow then please flag this in your PR and also make sure you've +given proper credit to the original author in the code snippet. + ## Style Guide `libgit2` is written in [ANSI C](http://en.wikipedia.org/wiki/ANSI_C) diff --git a/COPYING b/COPYING index d1ca4d401..f7e9f3af7 100644 --- a/COPYING +++ b/COPYING @@ -928,3 +928,66 @@ necessary. Here is a sample; alter the names: Ty Coon, President of Vice That's all there is to it! + +---------------------------------------------------------------------- + +Portions of src/win32/posix_w32.c are derrived from link_win32.c in PHP: + +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + diff --git a/Makefile.embed b/Makefile.embed index 76b4d3cda..eb8a78ebf 100644 --- a/Makefile.embed +++ b/Makefile.embed @@ -1,26 +1,44 @@ -PLATFORM=$(shell uname -o) +PLATFORM=$(shell uname -s) + +ifneq (,$(CROSS_COMPILE)) + PREFIX=$(CROSS_COMPILE)- +else + PREFIX= +endif + +MINGW=0 +ifneq (,$(findstring MINGW32,$(PLATFORM))) + MINGW=1 +endif +ifneq (,$(findstring mingw,$(CROSS_COMPILE))) + MINGW=1 +endif rm=rm -f -AR=ar cq -RANLIB=ranlib +AR=$(PREFIX)ar cq +RANLIB=$(PREFIX)ranlib + LIBNAME=libgit2.a -ifeq ($(PLATFORM),Msys) + +ifeq ($(MINGW),1) CC=gcc else CC=cc endif +CC:=$(PREFIX)$(CC) + INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES) -CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS) +CFLAGS= -g $(DEFINES) -Wall -Wextra -Wno-missing-field-initializers -O2 $(EXTRA_CFLAGS) SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c -ifeq ($(PLATFORM),Msys) +ifeq ($(MINGW),1) SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c INCLUDES += -Ideps/regex - DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 + DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1 else SRCS += $(wildcard src/unix/*.c) CFLAGS += -fPIC diff --git a/README.md b/README.md index a2a18765a..8ce60af62 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,36 @@ libgit2 - the Git linkable library -====================== +================================== [![Build Status](https://secure.travis-ci.org/libgit2/libgit2.png?branch=development)](http://travis-ci.org/libgit2/libgit2) -libgit2 is a portable, pure C implementation of the Git core methods provided as a +`libgit2` is a portable, pure C implementation of the Git core methods provided as a re-entrant linkable library with a solid API, allowing you to write native speed custom Git applications in any language with bindings. -libgit2 is licensed under a **very permissive license** (GPLv2 with a special Linking Exception). -This basically means that you can link it (unmodified) with any kind of software without having to -release its source code. +`libgit2` is licensed under a **very permissive license** (GPLv2 with a special +Linking Exception). This basically means that you can link it (unmodified) +with any kind of software without having to release its source code. +Additionally, the example code has been released to the public domain (see the +[separate license](examples/COPYING) for more information). -* Mailing list: ~~~~ - The libgit2 mailing list has - traditionally been hosted in Librelist, but Librelist is and has always - been a shitshow. We encourage you to [open an issue](https://github.com/libgit2/libgit2/issues) - on GitHub instead for any questions regarding the library. - * Archives: -* Website: +* 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: -* IRC: #libgit2 on irc.freenode.net. +* 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 + [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) instead for any questions regarding + the library, or [open an issue](https://github.com/libgit2/libgit2/issues) + on GitHub for bug reports. The mailing list archives are still available at + . + What It Can Do -================================== +============== -libgit2 is already very usable. +`libgit2` is already very usable and is being used in production for many applications including the GitHub.com site, in Plastic SCM +and also powering Microsoft's Visual Studio tools for Git. The library provides: * SHA conversions, formatting and shortening * abstracted ODB backend system @@ -39,15 +45,26 @@ libgit2 is already very usable. * descriptive and detailed error messages * ...and more (over 175 different API calls) +Optional dependencies +===================== + +While the library provides git functionality without the need for +dependencies, it can make use of a few libraries to add to it: + +- pthreads (non-Windows) to enable threadsafe access as well as multi-threaded pack generation +- OpenSSL (non-Windows) to talk over HTTPS and provide the SHA-1 functions +- LibSSH2 to enable the ssh transport +- iconv (OSX) to handle the HFS+ path encoding peculiarities + Building libgit2 - Using CMake ============================== -libgit2 builds cleanly on most platforms without any external dependencies. +`libgit2` builds cleanly on most platforms without any external dependencies. Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available; they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API for threading. -The libgit2 library is built using CMake 2.6+ () on all platforms. +The `libgit2` library is built using `CMake 2.6+` () on all platforms. On most systems you can build the library using the following commands @@ -104,6 +121,28 @@ See [the wiki] (https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows) for more detailed instructions. +Android +------- + +Extract toolchain from NDK using, `make-standalone-toolchain.sh` script. +Optionally, crosscompile and install OpenSSL inside of it. Then create CMake +toolchain file that configures paths to your crosscompiler (substitute `{PATH}` +with full path to the toolchain): + + SET(CMAKE_SYSTEM_NAME Linux) + SET(CMAKE_SYSTEM_VERSION Android) + + SET(CMAKE_C_COMPILER {PATH}/bin/arm-linux-androideabi-gcc) + SET(CMAKE_CXX_COMPILER {PATH}/bin/arm-linux-androideabi-g++) + SET(CMAKE_FIND_ROOT_PATH {PATH}/sysroot/) + + SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +Add `-DCMAKE_TOOLCHAIN_FILE={pathToToolchainFile} -DANDROID=1` to cmake command +when configuring. + Language Bindings ================================== @@ -118,9 +157,9 @@ Here are the bindings to libgit2 that are currently available: * Delphi * GitForDelphi * Erlang - * Geef + * Geef * Go - * go-git + * git2go * GObject * libgit2-glib * Haskell @@ -128,8 +167,8 @@ Here are the bindings to libgit2 that are currently available: * Lua * luagit2 * .NET - * libgit2net, low level bindings * libgit2sharp + * libgit2net, low level bindings superseded by libgit2sharp * Node.js * node-gitteh * nodegit @@ -161,9 +200,9 @@ Check the [contribution guidelines](CONTRIBUTING.md). License ================================== -libgit2 is under GPL2 **with linking exemption**. This means you -can link to the library with any program, commercial, open source or -other. However, you cannot modify libgit2 and distribute it without +`libgit2` is under GPL2 **with linking exemption**. This means you +can link to and use the library from any program, proprietary or open source; paid +or gratis. However, you cannot modify libgit2 and distribute it without supplying the source. See the COPYING file for the full license text. diff --git a/docs/checkout-internals.md b/docs/checkout-internals.md index cb646da5d..6147ffdd8 100644 --- a/docs/checkout-internals.md +++ b/docs/checkout-internals.md @@ -71,19 +71,19 @@ Key Diff with 2 non-workdir iterators --------------------------------- - Old New - --- --- - 0 x x - nothing - 1 x B1 - added blob - 2 x T1 - added tree - 3 B1 x - removed blob - 4 B1 B1 - unmodified blob - 5 B1 B2 - modified blob - 6 B1 T1 - typechange blob -> tree - 7 T1 x - removed tree - 8 T1 B1 - typechange tree -> blob - 9 T1 T1 - unmodified tree - 10 T1 T2 - modified tree (implies modified/added/removed blob inside) +| | Old | New | | +|----|-----|-----|------------------------------------------------------------| +| 0 | x | x | nothing | +| 1 | x | B1 | added blob | +| 2 | x | T1 | added tree | +| 3 | B1 | x | removed blob | +| 4 | B1 | B1 | unmodified blob | +| 5 | B1 | B2 | modified blob | +| 6 | B1 | T1 | typechange blob -> tree | +| 7 | T1 | x | removed tree | +| 8 | T1 | B1 | typechange tree -> blob | +| 9 | T1 | T1 | unmodified tree | +| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) | Now, let's make the "New" iterator into a working directory iterator, so @@ -92,23 +92,23 @@ we replace "added" items with either untracked or ignored, like this: Diff with non-work & workdir iterators -------------------------------------- - Old New-WD - --- ------ - 0 x x - nothing - 1 x B1 - untracked blob - 2 x Bi - ignored file - 3 x T1 - untracked tree - 4 x Ti - ignored tree - 5 B1 x - removed blob - 6 B1 B1 - unmodified blob - 7 B1 B2 - modified blob - 8 B1 T1 - typechange blob -> tree - 9 B1 Ti - removed blob AND ignored tree as separate items - 10 T1 x - removed tree - 11 T1 B1 - typechange tree -> blob - 12 T1 Bi - removed tree AND ignored blob as separate items - 13 T1 T1 - unmodified tree - 14 T1 T2 - modified tree (implies modified/added/removed blob inside) +| | Old | New | | +|----|-----|-----|------------------------------------------------------------| +| 0 | x | x | nothing | +| 1 | x | B1 | untracked blob | +| 2 | x | Bi | ignored file | +| 3 | x | T1 | untracked tree | +| 4 | x | Ti | ignored tree | +| 5 | B1 | x | removed blob | +| 6 | B1 | B1 | unmodified blob | +| 7 | B1 | B2 | modified blob | +| 8 | B1 | T1 | typechange blob -> tree | +| 9 | B1 | Ti | removed blob AND ignored tree as separate items | +| 10 | T1 | x | removed tree | +| 11 | T1 | B1 | typechange tree -> blob | +| 12 | T1 | Bi | removed tree AND ignored blob as separate items | +| 13 | T1 | T1 | unmodified tree | +| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) | Note: if there is a corresponding entry in the old tree, then a working directory item won't be ignored (i.e. no Bi or Ti for tracked items). @@ -122,46 +122,47 @@ Checkout From 3 Iterators (2 not workdir, 1 workdir) (base == old HEAD; target == what to checkout; actual == working dir) - base target actual/workdir - ---- ------ ------ - 0 x x x - nothing - 1 x x B1/Bi/T1/Ti - untracked/ignored blob/tree (SAFE) - 2+ x B1 x - add blob (SAFE) - 3 x B1 B1 - independently added blob (FORCEABLE-2) - 4* x B1 B2/Bi/T1/Ti - add blob with content conflict (FORCEABLE-2) - 5+ x T1 x - add tree (SAFE) - 6* x T1 B1/Bi - add tree with blob conflict (FORCEABLE-2) - 7 x T1 T1/i - independently added tree (SAFE+MISSING) - 8 B1 x x - independently deleted blob (SAFE+MISSING) - 9- B1 x B1 - delete blob (SAFE) - 10- B1 x B2 - delete of modified blob (FORCEABLE-1) - 11 B1 x T1/Ti - independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) - 12 B1 B1 x - locally deleted blob (DIRTY || SAFE+CREATE) - 13+ B1 B2 x - update to deleted blob (SAFE+MISSING) - 14 B1 B1 B1 - unmodified file (SAFE) - 15 B1 B1 B2 - locally modified file (DIRTY) - 16+ B1 B2 B1 - update unmodified blob (SAFE) - 17 B1 B2 B2 - independently updated blob (FORCEABLE-1) - 18+ B1 B2 B3 - update to modified blob (FORCEABLE-1) - 19 B1 B1 T1/Ti - locally deleted blob AND untrack/ign tree (DIRTY) - 20* B1 B2 T1/Ti - update to deleted blob AND untrack/ign tree (F-1) - 21+ B1 T1 x - add tree with locally deleted blob (SAFE+MISSING) - 22* B1 T1 B1 - add tree AND deleted blob (SAFE) - 23* B1 T1 B2 - add tree with delete of modified blob (F-1) - 24 B1 T1 T1 - add tree with deleted blob (F-1) - 25 T1 x x - independently deleted tree (SAFE+MISSING) - 26 T1 x B1/Bi - independently deleted tree AND untrack/ign blob (F-1) - 27- T1 x T1 - deleted tree (MAYBE SAFE) - 28+ T1 B1 x - deleted tree AND added blob (SAFE+MISSING) - 29 T1 B1 B1 - independently typechanged tree -> blob (F-1) - 30+ T1 B1 B2 - typechange tree->blob with conflicting blob (F-1) - 31* T1 B1 T1/T2 - typechange tree->blob (MAYBE SAFE) - 32+ T1 T1 x - restore locally deleted tree (SAFE+MISSING) - 33 T1 T1 B1/Bi - locally typechange tree->untrack/ign blob (DIRTY) - 34 T1 T1 T1/T2 - unmodified tree (MAYBE SAFE) - 35+ T1 T2 x - update locally deleted tree (SAFE+MISSING) - 36* T1 T2 B1/Bi - update to tree with typechanged tree->blob conflict (F-1) - 37 T1 T2 T1/T2/T3 - update to existing tree (MAYBE SAFE) +| |base | target | actual/workdir | | +|-----|-----|------- |----------------|--------------------------------------------------------------------| +| 0 | x | x | x | nothing | +| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) | +| 2+ | x | B1 | x | add blob (SAFE) | +| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) | +| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) | +| 5+ | x | T1 | x | add tree (SAFE) | +| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) | +| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) | +| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) | +| 9- | B1 | x | B1 | delete blob (SAFE) | +| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) | +| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) | +| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) | +| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) | +| 14 | B1 | B1 | B1 | unmodified file (SAFE) | +| 15 | B1 | B1 | B2 | locally modified file (DIRTY) | +| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) | +| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) | +| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) | +| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) | +| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) | +| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) | +| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) | +| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) | +| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) | +| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) | +| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) | +| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) | +| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) | +| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) | +| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) | +| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) | +| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) | +| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) | +| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) | +| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) | +| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) | +| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) | + The number is followed by ' ' if no change is needed or '+' if the case needs to write to disk or '-' if something must be deleted and '*' if @@ -169,34 +170,34 @@ there should be a delete followed by an write. There are four tiers of safe cases: -- SAFE == completely safe to update -- SAFE+MISSING == safe except the workdir is missing the expect content -- MAYBE SAFE == safe if workdir tree matches (or is missing) baseline +* SAFE == completely safe to update +* SAFE+MISSING == safe except the workdir is missing the expect content +* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline content, which is unknown at this point -- FORCEABLE == conflict unless FORCE is given -- DIRTY == no conflict but change is not applied unless FORCE +* FORCEABLE == conflict unless FORCE is given +* DIRTY == no conflict but change is not applied unless FORCE Some slightly unusual circumstances: - 8 - parent dir is only deleted when file is, so parent will be left if - empty even though it would be deleted if the file were present - 11 - core git does not consider this a conflict but attempts to delete T1 - and gives "unable to unlink file" error yet does not skip the rest - of the operation - 12 - without FORCE file is left deleted (i.e. not restored) so new wd is - dirty (and warning message "D file" is printed), with FORCE, file is - restored. - 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8 - combined, but core git considers this a conflict unless forced. - 26 - This combines two cases (1 & 25) (and also implied 8 for tree content) - which are ok on their own, but core git treat this as a conflict. - If not forced, this is a conflict. If forced, this actually doesn't - have to write anything and leaves the new blob as an untracked file. - 32 - This is the only case where the baseline and target values match - and yet we will still write to the working directory. In all other - cases, if baseline == target, we don't touch the workdir (it is - either already right or is "dirty"). However, since this case also - implies that a ?/B1/x case will exist as well, it can be skipped. +* 8 - parent dir is only deleted when file is, so parent will be left if + empty even though it would be deleted if the file were present +* 11 - core git does not consider this a conflict but attempts to delete T1 + and gives "unable to unlink file" error yet does not skip the rest + of the operation +* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is + dirty (and warning message "D file" is printed), with FORCE, file is + restored. +* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8 + combined, but core git considers this a conflict unless forced. +* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content) + which are ok on their own, but core git treat this as a conflict. + If not forced, this is a conflict. If forced, this actually doesn't + have to write anything and leaves the new blob as an untracked file. +* 32 - This is the only case where the baseline and target values match + and yet we will still write to the working directory. In all other + cases, if baseline == target, we don't touch the workdir (it is + either already right or is "dirty"). However, since this case also + implies that a ?/B1/x case will exist as well, it can be skipped. Cases 3, 17, 24, 26, and 29 are all considered conflicts even though none of them will require making any updates to the working directory. diff --git a/docs/diff-internals.md b/docs/diff-internals.md index 53e71f5b5..cf8ad5383 100644 --- a/docs/diff-internals.md +++ b/docs/diff-internals.md @@ -45,44 +45,48 @@ Internal Objects * `git_diff_file_content` is an internal structure that represents the data on one side of an item to be diffed; it is an augmented `git_diff_file` with more flags and the actual file data. -** it is created from a repository plus a) a git_diff_file, b) a git_blob, + + * it is created from a repository plus a) a git_diff_file, b) a git_blob, or c) raw data and size -** there are three main operations on git_diff_file_content: -*** _initialization_ sets up the data structure and does what it can up to, - but not including loading and looking at the actual data -*** _loading_ loads the data, preprocesses it (i.e. applies filters) and - potentially analyzes it (to decide if binary) -*** _free_ releases loaded data and frees any allocated memory + * there are three main operations on git_diff_file_content: + + * _initialization_ sets up the data structure and does what it can up to, + but not including loading and looking at the actual data + * _loading_ loads the data, preprocesses it (i.e. applies filters) and + potentially analyzes it (to decide if binary) + * _free_ releases loaded data and frees any allocated memory * The internal structure of a `git_diff_patch` stores the actual diff between a pair of `git_diff_file_content` items -** it may be "unset" if the items are not diffable -** "empty" if the items are the same -** otherwise it will consist of a set of hunks each of which covers some - number of lines of context, additions and deletions -** a patch is created from two git_diff_file_content items -** a patch is fully instantiated in three phases: -*** initial creation and initialization -*** loading of data and preliminary data examination -*** diffing of data and optional storage of diffs -** (TBD) if a patch is asked to store the diffs and the size of the diff - is significantly smaller than the raw data of the two sides, then the - patch may be flattened using a pool of string data + + * it may be "unset" if the items are not diffable + * "empty" if the items are the same + * otherwise it will consist of a set of hunks each of which covers some + number of lines of context, additions and deletions + * a patch is created from two git_diff_file_content items + * a patch is fully instantiated in three phases: + + * initial creation and initialization + * loading of data and preliminary data examination + * diffing of data and optional storage of diffs + * (TBD) if a patch is asked to store the diffs and the size of the diff + is significantly smaller than the raw data of the two sides, then the + patch may be flattened using a pool of string data * `git_diff_output` is an internal structure that represents an output target for a `git_diff_patch` -** It consists of file, hunk, and line callbacks, plus a payload -** There is a standard flattened output that can be used for plain text output -** Typically we use a `git_xdiff_output` which drives the callbacks via the - xdiff code taken from core Git. + * It consists of file, hunk, and line callbacks, plus a payload + * There is a standard flattened output that can be used for plain text output + * Typically we use a `git_xdiff_output` which drives the callbacks via the + xdiff code taken from core Git. * `git_diff_driver` is an internal structure that encapsulates the logic for a given type of file -** a driver is looked up based on the name and mode of a file. -** the driver can then be used to: -*** determine if a file is binary (by attributes, by git_diff_options - settings, or by examining the content) -*** give you a function pointer that is used to evaluate function context - for hunk headers -** At some point, the logic for getting a filtered version of file content - or calculating the OID of a file may be moved into the driver. + * a driver is looked up based on the name and mode of a file. + * the driver can then be used to: + * determine if a file is binary (by attributes, by git_diff_options + settings, or by examining the content) + * give you a function pointer that is used to evaluate function context + for hunk headers + * At some point, the logic for getting a filtered version of file content + or calculating the OID of a file may be moved into the driver. diff --git a/docs/error-handling.md b/docs/error-handling.md index 655afeba8..2dbe64a71 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -1,111 +1,270 @@ Error reporting in libgit2 ========================== -Error reporting is performed on an explicit `git_error **` argument, which appears at the end of all API calls that can return an error. Yes, this does clutter the API. +Libgit2 tries to follow the POSIX style: functions return an `int` value +with 0 (zero) indicating success and negative values indicating an error. +There are specific negative error codes for each "expected failure" +(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing) +and a generic error code (-1) for all critical or non-specific failures +(e.g. running out of memory or system corruption). -When a function fails, an error is set on the error variable **and** returns one of the generic error codes. +When a negative value is returned, an error message is also set. The +message can be accessed via the `giterr_last` function which will return a +pointer to a `git_error` structure containing the error message text and +the class of error (i.e. what part of the library generated the error). + +For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`) +has two expected failure cases: the SHA is not found at all which returns +`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects +share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of +critical failures (such as a packfile being corrupted, a loose object +having the wrong access permissions, etc.) all of which will return -1. +When the object lookup is successful, it will return 0. + +If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using +CMake), then the error message will be kept in thread-local storage, so it +will not be modified by other threads. If threads are not enabled, then +the error message is in global data. + +All of the error return codes, the `git_error` type, the error access +functions, and the error classes are defined in `include/git2/errors.h`. +See the documentation there for details on the APIs for accessing, +clearing, and even setting error codes. + +When writing libgit2 code, please be smart and conservative when returning +error codes. Functions usually have a maximum of two or three "expected +errors" and in most cases only one. If you feel there are more possible +expected error scenarios, then the API you are writing may be at too high +a level for core libgit2. + +Example usage +------------- + +When using libgit2, you will typically capture the return value from +functions using an `int` variable and check to see if it is negative. +When that happens, you can, if you wish, look at the specific value or +look at the error message that was generated. ~~~c -int git_repository_open(git_repository **repository, const char *path, git_error **error) { - // perform some opening - if (p_exists(path) < 0) { - giterr_set(error, GITERR_REPOSITORY, "The path '%s' doesn't exist", path); - return GIT_ENOTFOUND; + git_repository *repo; + int error = git_repository_open(&repo, "path/to/repo"); + + if (error < 0) { + fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message); + exit(1); } - ... + ... use `repo` here ... - if (try_to_parse(path, error) < 0) - return GIT_ERROR; + git_repository_free(repo); /* void function - no error return code */ +} +~~~ + +Some of the error return values do have meaning. Optionally, you can look +at the specific error values to decide what to do. + +~~~c +{ + git_repository *repo; + const char *path = "path/to/repo"; + int error = git_repository_open(&repo, path); + + if (error < 0) { + if (error == GIT_ENOTFOUND) + fprintf(stderr, "Could not find repository at path '%s'\n", path); + else + fprintf(stderr, "Unable to open repository: %s\n", + giterr_last()->message); + exit(1); + } + + ... happy ... +} +~~~ + +Some of the higher-level language bindings may use a range of information +from libgit2 to convert error return codes into exceptions, including the +specific error return codes and even the class of error and the error +message returned by `giterr_last`, but the full range of that logic is +beyond the scope of this document. + +Example internal implementation +------------------------------- + +Internally, libgit2 detects error scenarios, records error messages, and +returns error values. Errors from low-level functions are generally +passed upwards (unless the higher level can either handle the error or +wants to translate the error into something more meaningful). + +~~~c +int git_repository_open(git_repository **repository, const char *path) +{ + /* perform some logic to open the repository */ + if (p_exists(path) < 0) { + giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path); + return GIT_ENOTFOUND; + } ... } ~~~ -The simple error API +The public error API -------------------- -- `void giterr_set(git_error **, int, const char *, ...)`: the main function used to set an error. It allocates a new error object and stores it in the passed error pointer. It has no return value. The arguments for `giterr_set` are as follows: +- `const git_error *giterr_last(void)`: The main function used to look up + the last error. This may return NULL if no error has occurred. + Otherwise this should return a `git_error` object indicating the class + of error and the error message that was generated by the library. - - `git_error **error_ptr`: the pointer where the error will be created. - - `int error_class`: the class for the error. This is **not** an error code: this is an specific enum that specifies the error family. The point is to map these families 1-1 with Exception types on higher level languages (e.g. GitRepositoryException) - - `const char *error_str, ...`: the error string, with optional formatting arguments + The last error is stored in thread-local storage when libgit2 is + compiled with thread support, so you do not have to worry about another + thread overwriting the value. When thread support is off, the last + error is a global value. -- `void giterr_free(git_error *)`: takes an error and frees it. This function is available in the external API. + _Note_ There are some known bugs in the library where this may return + NULL even when an error code was generated. Please report these as + bugs, but in the meantime, please code defensively and check for NULL + when calling this function. -- `void giterr_clear(git_error **)`: clears an error previously set in an error pointer, setting it to NULL and calling `giterr_free` on it. +- `void geterr_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. -- `void giterr_propagate(git_error **, git_error *)`: moves an error to a given error pointer, handling the case when the error pointer is NULL (in that case the error gets freed, because it cannot be propagated). + _Note_ There are some known bugs in the library where a low level + function's error message is not cleared by higher level code that + handles the error and returns zero. Please report these as bugs, but in + the meantime, a zero return value from a libgit2 API does not guarantee + that `giterr_last()` will return NULL. -The new error code return values --------------------------------- +- `void giterr_set_str(int error_class, const char *message)`: This + function can be used when writing a custom backend module to set the + libgit2 error message. See the documentation on this function for its + use. Normal usage of libgit2 will probably never need to call this API. -We are doing this the POSIX way: one error code for each "expected failure", and a generic error code for all the critical failures. +- `void giterr_set_oom(void)`: This is a standard function for reporting + an out-of-memory error. It is written in a manner that it doesn't have + to allocate any extra memory in order to record the error, so this is + the best way to report that scenario. -For instance: A reference lookup can have an expected failure (which is when the reference cannot be found), and a critical failure (which could be any of a long list of things that could go wrong, such as the refs packfile being corrupted, a loose ref being written with the wrong permissions, etc). We cannot have distinct error codes for every single error in the library, hence `git_reference_lookup` would return GIT_SUCCESS if the operation was successful, GIT_ENOTFOUND when the reference doesn't exist, and GIT_ERROR when an error happens -- **the error is then detailed in the `git_error` parameter**. +Deviations from the standard +---------------------------- -Please be smart when returning error codes. Functions have max two "expected errors", and in most cases only one. +There are some public functions that do not return `int` values. There +are two primary cases: + +* `void` return values: If a function has a `void` return, then it will + never fail. This primary will be used for object destructors. + +* `git_xyz *` return values: These are simple accessor functions where the + only meaningful error would typically be looking something up by index + and having the index be out of bounds. In those cases, the function + will typically return NULL. + +* Boolean return values: There are some cases where a function cannot fail + and wants to return a boolean value. In those cases, we try to return 1 + for true and 0 for false. These cases are rare and the return value for + the function should probably be an `unsigned int` to denote these cases. + If you find an exception, please open an issue and let's fix it. + +There are a few other exceptions to these rules here and there in the +library, but those are extremely rare and should probably be converted +over to other to more standard patterns for usage. Feel free to open +issues pointing these out. + +There are some known bugs in the library where some functions may return a +negative value but not set an error message and some other functions may +return zero (no error) and yet leave an error message set. Please report +these cases as issues and they will be fixed. In the meanwhile, please +code defensively, checking that the return value of `giterr_last` is not +NULL before using it, and not relying on `giterr_last` to return NULL when +a function returns 0 for success. + +The internal error API +---------------------- + +- `void giterr_set(int error_class, const char *fmt, ...)`: This is the + main internal function for setting an error. It works like `printf` to + format the error message. See the notes of `giterr_set_str` for a + general description of how error messages are stored (and also about + special handling for `error_class` of `GITERR_OS`). Writing error messages ---------------------- Here are some guidelines when writing error messages: -- Use proper English, and an impersonal or past tenses: *The given path does not exist*, *Failed to lookup object in ODB* +- Use proper English, and an impersonal or past tenses: *The given path + does not exist*, *Failed to lookup object in ODB* -- Use short, direct and objective messages. **One line, max**. libgit2 is a low level library: think that all the messages reported will be thrown as Ruby or Python exceptions. Think how long are common exception messages in those languages. +- Use short, direct and objective messages. **One line, max**. libgit2 is + a low level library: think that all the messages reported will be thrown + as Ruby or Python exceptions. Think how long are common exception + messages in those languages. -- **Do not add redundant information to the error message**, specially information that can be inferred from the context. +- **Do not add redundant information to the error message**, specially + information that can be inferred from the context. - E.g. in `git_repository_open`, do not report a message like "Failed to open repository: path not found". Somebody is - calling that function. If it fails, he already knows that the repository failed to open! + E.g. in `git_repository_open`, do not report a message like "Failed to + open repository: path not found". Somebody is calling that + function. If it fails, they already know that the repository failed to + open! General guidelines for error reporting -------------------------------------- -- We never handle programming errors with these functions. Programming errors are `assert`ed, and when their source is internal, fixed as soon as possible. This is C, people. +- Libgit2 does not handle programming errors with these + functions. Programming errors are `assert`ed, and when their source is + internal, fixed as soon as possible. This is C, people. - Example of programming errors that would **not** be handled: passing NULL to a function that expects a valid pointer; passing a `git_tree` to a function that expects a `git_commit`. All these cases need to be identified with `assert` and fixed asap. + Example of programming errors that would **not** be handled: passing + NULL to a function that expects a valid pointer; passing a `git_tree` + to a function that expects a `git_commit`. All these cases need to be + identified with `assert` and fixed asap. - Example of a runtime error: failing to parse a `git_tree` because it contains invalid data. Failing to open a file because it doesn't exist on disk. These errors would be handled, and a `git_error` would be set. + Example of a runtime error: failing to parse a `git_tree` because it + contains invalid data. Failing to open a file because it doesn't exist + on disk. These errors are handled, a meaningful error message is set, + and an error code is returned. -- The `git_error **` argument is always the last in the signature of all API calls. No exceptions. +- In general, *do not* try to overwrite errors internally and *do* + propagate error codes from lower level functions to the higher level. + There are some cases where propagating an error code will be more + confusing rather than less, so there are some exceptions to this rule, + but the default behavior should be to simply clean up and pass the error + on up to the caller. -- When the programmer (or us, internally) doesn't need error handling, he can pass `NULL` to the `git_error **` param. This means that the errors won't be *reported*, but obviously they still will be handled (i.e. the failing function will interrupt and return cleanly). This is transparently handled by `giterr_set` - -- `git_error *` **must be initialized to `NULL` before passing its value to a function!!** + **WRONG** ~~~c - git_error *err; - git_error *good_error = NULL; - - git_foo_func(arg1, arg2, &error); // invalid: `error` is not initialized - git_foo_func2(arg1, arg2, &good_error); // OK! - git_foo_func3(arg1, arg2, NULL); // OK! But no error reporting! - ~~~ - -- Piling up errors is an error! Don't do this! Errors must always be free'd when a function returns. - - ~~~c - git_error *error = NULL; - - git_foo_func1(arg1, &error); - git_foo_func2(arg2, &error); // WRONG! What if func1 failed? `error` would leak! - ~~~ - -- Likewise: do not rethrow errors internally! - - ~~~c - int git_commit_create(..., git_error **error) + int git_commit_parent(...) { - if (git_reference_exists("HEAD", error) < 0) { - /* HEAD does not exist; create it so we can commit... */ - if (git_reference_create("HEAD", error) < 0) { - /* error could be rethrown */ - } + ... + + if (git_commit_lookup(parent, repo, parent_id) < 0) { + giterr_set(GITERR_COMMIT, "Overwrite lookup error message"); + return -1; /* mask error code */ } -- Remember that errors are now allocated, and hence they need to be free'd after they've been used. Failure to do so internally (e.g. in the already seen examples of error piling) will be reported by Valgrind, so we can easily find where are we rethrowing errors. + ... + } + ~~~ -- Remember that any function that fails **will set an error object**, and that object will be freed. + **RIGHT** + + ~~~c + int git_commit_parent(...) + { + ... + + error = git_commit_lookup(parent, repo, parent_id); + if (error < 0) { + /* cleanup intermediate objects if necessary */ + /* leave error message and propagate error code */ + return error; + } + + ... + } + ~~~ diff --git a/examples/.gitignore b/examples/.gitignore index e8e0820a5..711493994 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -2,4 +2,10 @@ general showindex diff rev-list +blame +cat-file +init +log +rev-parse +status *.dSYM diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..596be45ed --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,16 @@ +FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h) +ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2}) +IF(WIN32 OR ANDROID) + TARGET_LINK_LIBRARIES(cgit2 git2) +ELSE() + TARGET_LINK_LIBRARIES(cgit2 git2 pthread) +ENDIF() + +FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c) +FOREACH(src_app ${SRC_EXAMPLE_APPS}) + STRING(REPLACE ".c" "" app_name ${src_app}) + IF(NOT ${app_name} STREQUAL "common") + ADD_EXECUTABLE(${app_name} ${src_app} "common.c") + TARGET_LINK_LIBRARIES(${app_name} git2) + ENDIF() +ENDFOREACH() diff --git a/examples/COPYING b/examples/COPYING new file mode 100644 index 000000000..0e259d42c --- /dev/null +++ b/examples/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/examples/Makefile b/examples/Makefile index 140cc4da9..2e7f68f82 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,12 +3,12 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status +APPS = general showindex diff rev-list cat-file status log rev-parse init blame all: $(APPS) % : %.c - $(CC) -o $@ $(CFLAGS) $< $(LFLAGS) + $(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS) clean: $(RM) $(APPS) diff --git a/examples/README.md b/examples/README.md index f2b6d7d23..769c4b267 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,11 +1,22 @@ libgit2 examples ================ -These examples are meant as thin, easy-to-read snippets for Docurium -(https://github.com/github/docurium) rather than full-blown -implementations of Git commands. They are not vetted as carefully -for bugs, error handling, or cross-platform compatibility as the -rest of the code in libgit2, so copy with some caution. +These examples are a mixture of basic emulation of core Git command line +functions and simple snippets demonstrating libgit2 API usage (for use +with Docurium). As a whole, they are not vetted carefully for bugs, error +handling, and cross-platform compatibility in the same manner as the rest +of the code in libgit2, so copy with caution. -For HTML versions, check "Examples" at http://libgit2.github.com/libgit2 +That being said, you are welcome to copy code from these examples as +desired when using libgit2. They have been [released to the public domain][cc0], +so there are no restrictions on their use. +[cc0]: COPYING + +For annotated HTML versions, see the "Examples" section of: + + http://libgit2.github.com/libgit2 + +such as: + + http://libgit2.github.com/libgit2/ex/HEAD/general.html diff --git a/examples/add.c b/examples/add.c new file mode 100644 index 000000000..336596bde --- /dev/null +++ b/examples/add.c @@ -0,0 +1,159 @@ +/* + * libgit2 "add" example - shows how to modify the index + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" +#include + +enum print_options { + SKIP = 1, + VERBOSE = 2, + UPDATE = 4, +}; + +struct print_payload { + enum print_options options; + git_repository *repo; +}; + +/* Forward declarations for helpers */ +static void parse_opts(int *options, int *count, int argc, char *argv[]); +void init_array(git_strarray *array, int argc, char **argv); +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload); + +int main (int argc, char** argv) +{ + git_index_matched_path_cb matched_cb = NULL; + git_repository *repo = NULL; + git_index *index; + git_strarray array = {0}; + int options = 0, count = 0; + struct print_payload payload = {0}; + + git_threads_init(); + + parse_opts(&options, &count, argc, argv); + + init_array(&array, argc-count, argv+count); + + check_lg2(git_repository_open(&repo, "."), "No git repository", NULL); + check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); + + if (options&VERBOSE || options&SKIP) { + matched_cb = &print_matched_cb; + } + + payload.options = options; + payload.repo = repo; + + if (options&UPDATE) { + git_index_update_all(index, &array, matched_cb, &payload); + } else { + git_index_add_all(index, &array, 0, matched_cb, &payload); + } + + git_index_write(index); + git_index_free(index); + git_repository_free(repo); + + git_threads_shutdown(); + + return 0; +} + +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) +{ + struct print_payload p = *(struct print_payload*)(payload); + int ret; + git_status_t status; + (void)matched_pathspec; + + if (git_status_file(&status, p.repo, path)) { + return -1; //abort + } + + if (status & GIT_STATUS_WT_MODIFIED || + status & GIT_STATUS_WT_NEW) { + printf("add '%s'\n", path); + ret = 0; + } else { + ret = 1; + } + + if(p.options & SKIP) { + ret = 1; + } + + return ret; +} + +void init_array(git_strarray *array, int argc, char **argv) +{ + unsigned int i; + + array->count = argc; + array->strings = malloc(sizeof(char*) * array->count); + assert(array->strings!=NULL); + + for(i=0; icount; i++) { + array->strings[i]=argv[i]; + } + + return; +} + +void print_usage(void) +{ + fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n"); + fprintf(stderr, "\t-n, --dry-run dry run\n"); + fprintf(stderr, "\t-v, --verbose be verbose\n"); + fprintf(stderr, "\t-u, --update update tracked files\n"); + exit(1); +} + +static void parse_opts(int *options, int *count, int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + break; + } + else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { + *options |= VERBOSE; + } + else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { + *options |= SKIP; + } + else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { + *options |= UPDATE; + } + else if(!strcmp(argv[i], "-h")) { + print_usage(); + break; + } + else if(!strcmp(argv[i], "--")) { + i++; + break; + } + else { + fprintf(stderr, "Unsupported option %s.\n", argv[i]); + print_usage(); + } + } + + if (argc<=i) + print_usage(); + + *count = i; +} diff --git a/examples/blame.c b/examples/blame.c new file mode 100644 index 000000000..06310d540 --- /dev/null +++ b/examples/blame.c @@ -0,0 +1,199 @@ +/* + * libgit2 "blame" example - shows how to use the blame API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** + * This example demonstrates how to invoke the libgit2 blame API to roughly + * simulate the output of `git blame` and a few of its command line arguments. + */ + +struct opts { + char *path; + char *commitspec; + int C; + int M; + int start_line; + int end_line; +}; +static void parse_opts(struct opts *o, int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + int i, line, break_on_null_hunk; + char spec[1024] = {0}; + struct opts o = {0}; + const char *rawdata; + git_repository *repo = NULL; + git_revspec revspec = {0}; + git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + git_blob *blob; + git_object *obj; + + git_threads_init(); + + parse_opts(&o, argc, argv); + if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + + /** Open the repository. */ + check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL); + + /** + * The commit range comes in "commitish" form. Use the rev-parse API to + * nail down the end points. + */ + if (o.commitspec) { + check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL); + if (revspec.flags & GIT_REVPARSE_SINGLE) { + git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from)); + git_object_free(revspec.from); + } else { + git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from)); + git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to)); + git_object_free(revspec.from); + git_object_free(revspec.to); + } + } + + /** Run the blame. */ + check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL); + + /** + * Get the raw data inside the blob for output. We use the + * `commitish:path/to/file.txt` format to find it. + */ + if (git_oid_iszero(&blameopts.newest_commit)) + strcpy(spec, "HEAD"); + else + git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit); + strcat(spec, ":"); + strcat(spec, o.path); + + check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL); + check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL); + git_object_free(obj); + + rawdata = git_blob_rawcontent(blob); + + /** Produce the output. */ + line = 1; + i = 0; + break_on_null_hunk = 0; + while (i < git_blob_rawsize(blob)) { + const char *eol = strchr(rawdata+i, '\n'); + char oid[10] = {0}; + const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line); + + if (break_on_null_hunk && !hunk) break; + + if (hunk) { + break_on_null_hunk = 1; + char sig[128] = {0}; + + git_oid_tostr(oid, 10, &hunk->final_commit_id); + snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email); + + printf("%s ( %-30s %3d) %.*s\n", + oid, + sig, + line, + (int)(eol-rawdata-i), + rawdata+i); + } + + i = (int)(eol - rawdata + 1); + line++; + } + + /** Cleanup. */ + git_blob_free(blob); + git_blame_free(blame); + git_repository_free(repo); + + git_threads_shutdown(); + + return 0; +} + +/** Tell the user how to make this thing work. */ +static void usage(const char *msg, const char *arg) +{ + if (msg && arg) + fprintf(stderr, "%s: %s\n", msg, arg); + else if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "usage: blame [options] [] \n"); + fprintf(stderr, "\n"); + fprintf(stderr, " example: `HEAD~10..HEAD`, or `1234abcd`\n"); + fprintf(stderr, " -L process only line range n-m, counting from 1\n"); + fprintf(stderr, " -M find line moves within and across files\n"); + fprintf(stderr, " -C find line copies within and across files\n"); + fprintf(stderr, "\n"); + exit(1); +} + +/** Parse the arguments. */ +static void parse_opts(struct opts *o, int argc, char *argv[]) +{ + int i; + char *bare_args[3] = {0}; + + if (argc < 2) usage(NULL, NULL); + + for (i=1; i= 3) + usage("Invalid argument set", NULL); + bare_args[i] = a; + } + else if (!strcmp(a, "--")) + continue; + else if (!strcasecmp(a, "-M")) + o->M = 1; + else if (!strcasecmp(a, "-C")) + o->C = 1; + else if (!strcasecmp(a, "-L")) { + i++; a = argv[i]; + if (i >= argc) fatal("Not enough arguments to -L", NULL); + check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL); + } + else { + /* commit range */ + if (o->commitspec) fatal("Only one commit spec allowed", NULL); + o->commitspec = a; + } + } + + /* Handle the bare arguments */ + if (!bare_args[0]) usage("Please specify a path", NULL); + o->path = bare_args[0]; + if (bare_args[1]) { + /* */ + o->path = bare_args[1]; + o->commitspec = bare_args[0]; + } + if (bare_args[2]) { + /* */ + char spec[128] = {0}; + o->path = bare_args[2]; + sprintf(spec, "%s..%s", bare_args[0], bare_args[1]); + o->commitspec = spec; + } +} diff --git a/examples/cat-file.c b/examples/cat-file.c index ebb6cb0ca..fa6add07b 100644 --- a/examples/cat-file.c +++ b/examples/cat-file.c @@ -1,37 +1,18 @@ -#include -#include -#include -#include +/* + * libgit2 "cat-file" example - shows how to print data from the ODB + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ -static git_repository *g_repo; - -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} - -static void usage(const char *message, const char *arg) -{ - if (message && arg) - fprintf(stderr, "%s: %s\n", message, arg); - else if (message) - fprintf(stderr, "%s\n", message); - fprintf(stderr, "usage: cat-file (-t | -s | -e | -p) [] \n"); - exit(1); -} - -static int check_str_param( - const char *arg, const char *pattern, const char **val) -{ - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; -} +#include "common.h" static void print_signature(const char *header, const git_signature *sig) { @@ -57,12 +38,14 @@ static void print_signature(const char *header, const git_signature *sig) sign, hours, minutes); } +/** Printing out a blob is simple, get the contents and print */ static void show_blob(const git_blob *blob) { /* ? Does this need crlf filtering? */ fwrite(git_blob_rawcontent(blob), git_blob_rawsize(blob), 1, stdout); } +/** Show each entry with its type, id and attributes */ static void show_tree(const git_tree *tree) { size_t i, max_i = (int)git_tree_entrycount(tree); @@ -81,6 +64,9 @@ static void show_tree(const git_tree *tree) } } +/** + * Commits and tags have a few interesting fields in their header. + */ static void show_commit(const git_commit *commit) { unsigned int i, max_i; @@ -123,53 +109,34 @@ enum { SHOW_PRETTY = 4 }; +/* Forward declarations for option-parsing helper */ +struct opts { + const char *dir; + const char *rev; + int action; + int verbose; +}; +static void parse_opts(struct opts *o, int argc, char *argv[]); + + +/** Entry point for this command */ int main(int argc, char *argv[]) { - const char *dir = ".", *rev = NULL; - int i, action = 0, verbose = 0; + git_repository *repo; + struct opts o = { ".", NULL, 0, 0 }; git_object *obj = NULL; char oidstr[GIT_OID_HEXSZ + 1]; git_threads_init(); - for (i = 1; i < argc; ++i) { - char *a = argv[i]; + parse_opts(&o, argc, argv); - if (a[0] != '-') { - if (rev != NULL) - usage("Only one rev should be provided", NULL); - else - rev = a; - } - else if (!strcmp(a, "-t")) - action = SHOW_TYPE; - else if (!strcmp(a, "-s")) - action = SHOW_SIZE; - else if (!strcmp(a, "-e")) - action = SHOW_NONE; - else if (!strcmp(a, "-p")) - action = SHOW_PRETTY; - else if (!strcmp(a, "-q")) - verbose = 0; - else if (!strcmp(a, "-v")) - verbose = 1; - else if (!strcmp(a, "--help") || !strcmp(a, "-h")) - usage(NULL, NULL); - else if (!check_str_param(a, "--git-dir=", &dir)) - usage("Unknown option", a); - } + check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), + "Could not open repository", NULL); + check_lg2(git_revparse_single(&obj, repo, o.rev), + "Could not resolve", o.rev); - if (!action || !rev) - usage(NULL, NULL); - - check(git_repository_open_ext(&g_repo, dir, 0, NULL), - "Could not open repository"); - - if (git_revparse_single(&obj, g_repo, rev) < 0) { - fprintf(stderr, "Could not resolve '%s'\n", rev); - exit(1); - } - if (verbose) { + if (o.verbose) { char oidstr[GIT_OID_HEXSZ + 1]; git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj)); @@ -177,7 +144,7 @@ int main(int argc, char *argv[]) git_object_type2string(git_object_type(obj)), oidstr); } - switch (action) { + switch (o.action) { case SHOW_TYPE: printf("%s\n", git_object_type2string(git_object_type(obj))); break; @@ -185,9 +152,9 @@ int main(int argc, char *argv[]) git_odb *odb; git_odb_object *odbobj; - check(git_repository_odb(&odb, g_repo), "Could not open ODB"); - check(git_odb_read(&odbobj, odb, git_object_id(obj)), - "Could not find obj"); + check_lg2(git_repository_odb(&odb, repo), "Could not open ODB", NULL); + check_lg2(git_odb_read(&odbobj, odb, git_object_id(obj)), + "Could not find obj", NULL); printf("%ld\n", (long)git_odb_object_size(odbobj)); @@ -221,9 +188,59 @@ int main(int argc, char *argv[]) } git_object_free(obj); - git_repository_free(g_repo); + git_repository_free(repo); git_threads_shutdown(); return 0; } + +/** Print out usage information */ +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, + "usage: cat-file (-t | -s | -e | -p) [-v] [-q] " + "[-h|--help] [--git-dir=] \n"); + exit(1); +} + +/** Parse the command-line options taken from git */ +static void parse_opts(struct opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] != '-') { + if (o->rev != NULL) + usage("Only one rev should be provided", NULL); + else + o->rev = a; + } + else if (!strcmp(a, "-t")) + o->action = SHOW_TYPE; + else if (!strcmp(a, "-s")) + o->action = SHOW_SIZE; + else if (!strcmp(a, "-e")) + o->action = SHOW_NONE; + else if (!strcmp(a, "-p")) + o->action = SHOW_PRETTY; + else if (!strcmp(a, "-q")) + o->verbose = 0; + else if (!strcmp(a, "-v")) + o->verbose = 1; + else if (!strcmp(a, "--help") || !strcmp(a, "-h")) + usage(NULL, NULL); + else if (!match_str_arg(&o->dir, &args, "--git-dir")) + usage("Unknown option", a); + } + + if (!o->action || !o->rev) + usage(NULL, NULL); + +} diff --git a/examples/common.c b/examples/common.c new file mode 100644 index 000000000..12dbccf59 --- /dev/null +++ b/examples/common.c @@ -0,0 +1,191 @@ +/* + * Utilities library for libgit2 examples + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +void check_lg2(int error, const char *message, const char *extra) +{ + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) + return; + + if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + exit(1); +} + +void fatal(const char *message, const char *extra) +{ + if (extra) + fprintf(stderr, "%s %s\n", message, extra); + else + fprintf(stderr, "%s\n", message); + + exit(1); +} + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +int match_str_arg( + const char **out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected value following argument", opt); + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +static const char *match_numeric_arg(struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return NULL; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected numeric value following argument", opt); + args->pos += 1; + found = args->argv[args->pos]; + } else { + found = found + len; + if (*found == '=') + found++; + } + + return found; +} + +int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint16_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +static int match_int_internal( + int *out, const char *str, int allow_negative, const char *opt) +{ + char *endptr = NULL; + int val = (int)strtol(str, &endptr, 10); + + if (!endptr || *endptr != '\0') + fatal("expected number", opt); + else if (val < 0 && !allow_negative) + fatal("negative values are not allowed", opt); + + if (out) + *out = val; + + return 1; +} + +int is_integer(int *out, const char *str, int allow_negative) +{ + return match_int_internal(out, str, allow_negative, NULL); +} + +int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative) +{ + const char *found = match_numeric_arg(args, opt); + if (!found) + return 0; + return match_int_internal(out, found, allow_negative, opt); +} + +int diff_output( + const git_diff_delta *d, + const git_diff_hunk *h, + const git_diff_line *l, + void *p) +{ + FILE *fp = p; + + (void)d; (void)h; + + if (!fp) + fp = stdout; + + if (l->origin == GIT_DIFF_LINE_CONTEXT || + l->origin == GIT_DIFF_LINE_ADDITION || + l->origin == GIT_DIFF_LINE_DELETION) + fputc(l->origin, fp); + + fwrite(l->content, 1, l->content_len, fp); + + return 0; +} + +void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish) +{ + git_object *obj = NULL; + + check_lg2( + git_revparse_single(&obj, repo, treeish), + "looking up object", treeish); + + check_lg2( + git_object_peel((git_object **)out, obj, GIT_OBJ_TREE), + "resolving object to tree", treeish); + + git_object_free(obj); +} + diff --git a/examples/common.h b/examples/common.h new file mode 100644 index 000000000..2fd7d579f --- /dev/null +++ b/examples/common.h @@ -0,0 +1,87 @@ +/* + * Utilities library for libgit2 examples + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include +#include +#include +#include + +/** + * Check libgit2 error code, printing error to stderr on failure and + * exiting the program. + */ +extern void check_lg2(int error, const char *message, const char *extra); + +/** + * Exit the program, printing error to stderr + */ +extern void fatal(const char *message, const char *extra); + +/** + * Check if a string has the given prefix. Returns 0 if not prefixed + * or the length of the prefix if it is. + */ +extern size_t is_prefixed(const char *str, const char *pfx); + +/** + * Match an integer string, returning 1 if matched, 0 if not. + */ +extern int is_integer(int *out, const char *str, int allow_negative); + +struct args_info { + int argc; + char **argv; + int pos; +}; +#define ARGS_INFO_INIT { argc, argv, 0 } + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; otherwise return 0. + */ +extern int match_str_arg( + const char **out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint16. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint16_t value; otherwise return 0. + */ +extern int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as int. If + * `opt` matches exactly, take the next arg as an int value; if it matches + * as a prefix (equal sign optional), take the remainder of the arg as a + * int value; otherwise return 0. + */ +extern int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative); + +/** + * Basic output function for plain text diff output + * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`) + */ +extern int diff_output( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); + +/** + * Convert a treeish argument to an actual tree; this will call check_lg2 + * and exit the program if `treeish` cannot be resolved to a tree + */ +extern void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish); diff --git a/examples/diff.c b/examples/diff.c index 11efa21ec..daf5d7030 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -1,41 +1,31 @@ -#include -#include -#include -#include +/* + * libgit2 "diff" example - shows how to use the diff API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} +#include "common.h" -static int resolve_to_tree( - git_repository *repo, const char *identifier, git_tree **tree) -{ - int err = 0; - git_object *obj = NULL; +/** + * This example demonstrates the use of the libgit2 diff APIs to + * create `git_diff` objects and display them, emulating a number of + * core Git `diff` command line options. + * + * This covers on a portion of the core Git diff options and doesn't + * have particularly good error handling, but it should show most of + * the core libgit2 diff APIs, including various types of diffs and + * how to do renaming detection and patch formatting. + */ - if ((err = git_revparse_single(&obj, repo, identifier)) < 0) - return err; - - switch (git_object_type(obj)) { - case GIT_OBJ_TREE: - *tree = (git_tree *)obj; - break; - case GIT_OBJ_COMMIT: - err = git_commit_tree(tree, (git_commit *)obj); - git_object_free(obj); - break; - default: - err = GIT_ENOTFOUND; - } - - return err; -} - -char *colors[] = { +static const char *colors[] = { "\033[m", /* reset */ "\033[1m", /* bold */ "\033[31m", /* red */ @@ -43,67 +33,112 @@ char *colors[] = { "\033[36m" /* cyan */ }; -static int printer( - const git_diff_delta *delta, - const git_diff_range *range, - char usage, - const char *line, - size_t line_len, - void *data) +/** The 'opts' struct captures all the various parsed command line options. */ +struct opts { + git_diff_options diffopts; + git_diff_find_options findopts; + int color; + int cached; + git_diff_format_t format; + const char *treeish1; + const char *treeish2; + const char *dir; +}; + +/** These functions are implemented at the end */ +static void parse_opts(struct opts *o, int argc, char *argv[]); +static int color_printer( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); + +int main(int argc, char *argv[]) { - int *last_color = data, color = 0; + git_repository *repo = NULL; + git_tree *t1 = NULL, *t2 = NULL; + git_diff *diff; + struct opts o = { + GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, + -1, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." + }; - (void)delta; (void)range; (void)line_len; + git_threads_init(); - if (*last_color >= 0) { - switch (usage) { - case GIT_DIFF_LINE_ADDITION: color = 3; break; - case GIT_DIFF_LINE_DELETION: color = 2; break; - case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; - case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; - case GIT_DIFF_LINE_FILE_HDR: color = 1; break; - case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; - default: color = 0; - } - if (color != *last_color) { - if (*last_color == 1 || color == 1) - fputs(colors[0], stdout); - fputs(colors[color], stdout); - *last_color = color; - } + parse_opts(&o, argc, argv); + + check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), + "Could not open repository", o.dir); + + /** + * Possible argument patterns: + * + * * <sha1> <sha2> + * * <sha1> --cached + * * <sha1> + * * --cached + * * nothing + * + * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> + * are not supported in this example + */ + + if (o.treeish1) + treeish_to_tree(&t1, repo, o.treeish1); + if (o.treeish2) + treeish_to_tree(&t2, repo, o.treeish2); + + if (t1 && t2) + check_lg2( + git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), + "diff trees", NULL); + else if (t1 && o.cached) + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", NULL); + else if (t1) + check_lg2( + git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), + "diff tree to working directory", NULL); + else if (o.cached) { + treeish_to_tree(&t1, repo, "HEAD"); + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", NULL); } + else + check_lg2( + git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), + "diff index to working directory", NULL); + + /** Apply rename and copy detection if requested. */ + + if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) + check_lg2( + git_diff_find_similar(diff, &o.findopts), + "finding renames and copies", NULL); + + /** Generate simple output using libgit2 display helper. */ + + if (o.color >= 0) + fputs(colors[0], stdout); + + check_lg2( + git_diff_print(diff, o.format, color_printer, &o.color), + "displaying diff", NULL); + + if (o.color >= 0) + fputs(colors[0], stdout); + + /** Cleanup before exiting. */ + + git_diff_free(diff); + git_tree_free(t1); + git_tree_free(t2); + git_repository_free(repo); + + git_threads_shutdown(); - fputs(line, stdout); return 0; } -static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) -{ - size_t len = strlen(pattern); - uint16_t strval; - char *endptr = NULL; - if (strncmp(arg, pattern, len)) - return 0; - if (arg[len] == '\0' && pattern[len - 1] != '=') - return 1; - if (arg[len] == '=') - len++; - strval = strtoul(arg + len, &endptr, 0); - if (endptr == arg) - return 0; - *val = strval; - return 1; -} - -static int check_str_param(const char *arg, const char *pattern, const char **val) -{ - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; -} - static void usage(const char *message, const char *arg) { if (message && arg) @@ -114,152 +149,109 @@ static void usage(const char *message, const char *arg) exit(1); } -enum { - FORMAT_PATCH = 0, - FORMAT_COMPACT = 1, - FORMAT_RAW = 2 -}; - -int main(int argc, char *argv[]) +/** This implements very rudimentary colorized output. */ +static int color_printer( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *data) { - git_repository *repo = NULL; - git_tree *t1 = NULL, *t2 = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - git_diff_list *diff; - int i, color = -1, format = FORMAT_PATCH, cached = 0; - char *a, *treeish1 = NULL, *treeish2 = NULL; - const char *dir = "."; + int *last_color = data, color = 0; - git_threads_init(); + (void)delta; (void)hunk; - /* parse arguments as copied from git-diff */ + if (*last_color >= 0) { + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: color = 3; break; + case GIT_DIFF_LINE_DELETION: color = 2; break; + case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; + case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; + case GIT_DIFF_LINE_FILE_HDR: color = 1; break; + case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; + default: break; + } - for (i = 1; i < argc; ++i) { - a = argv[i]; + if (color != *last_color) { + if (*last_color == 1 || color == 1) + fputs(colors[0], stdout); + fputs(colors[color], stdout); + *last_color = color; + } + } + + return diff_output(delta, hunk, line, stdout); +} + +/** Parse arguments as copied from git-diff. */ +static void parse_opts(struct opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; if (a[0] != '-') { - if (treeish1 == NULL) - treeish1 = a; - else if (treeish2 == NULL) - treeish2 = a; + if (o->treeish1 == NULL) + o->treeish1 = a; + else if (o->treeish2 == NULL) + o->treeish2 = a; else usage("Only one or two tree identifiers can be provided", NULL); } else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) - format = FORMAT_PATCH; + o->format = GIT_DIFF_FORMAT_PATCH; else if (!strcmp(a, "--cached")) - cached = 1; + o->cached = 1; + else if (!strcmp(a, "--name-only")) + o->format = GIT_DIFF_FORMAT_NAME_ONLY; else if (!strcmp(a, "--name-status")) - format = FORMAT_COMPACT; + o->format = GIT_DIFF_FORMAT_NAME_STATUS; else if (!strcmp(a, "--raw")) - format = FORMAT_RAW; + o->format = GIT_DIFF_FORMAT_RAW; else if (!strcmp(a, "--color")) - color = 0; + o->color = 0; else if (!strcmp(a, "--no-color")) - color = -1; + o->color = -1; else if (!strcmp(a, "-R")) - opts.flags |= GIT_DIFF_REVERSE; + o->diffopts.flags |= GIT_DIFF_REVERSE; else if (!strcmp(a, "-a") || !strcmp(a, "--text")) - opts.flags |= GIT_DIFF_FORCE_TEXT; + o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; else if (!strcmp(a, "--ignore-space-at-eol")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; else if (!strcmp(a, "--ignored")) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; else if (!strcmp(a, "--untracked")) - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - else if (check_uint16_param(a, "-M", &findopts.rename_threshold) || - check_uint16_param(a, "--find-renames", - &findopts.rename_threshold)) - findopts.flags |= GIT_DIFF_FIND_RENAMES; - else if (check_uint16_param(a, "-C", &findopts.copy_threshold) || - check_uint16_param(a, "--find-copies", - &findopts.copy_threshold)) - findopts.flags |= GIT_DIFF_FIND_COPIES; + o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + else if (match_uint16_arg( + &o->findopts.rename_threshold, &args, "-M") || + match_uint16_arg( + &o->findopts.rename_threshold, &args, "--find-renames")) + o->findopts.flags |= GIT_DIFF_FIND_RENAMES; + else if (match_uint16_arg( + &o->findopts.copy_threshold, &args, "-C") || + match_uint16_arg( + &o->findopts.copy_threshold, &args, "--find-copies")) + o->findopts.flags |= GIT_DIFF_FIND_COPIES; else if (!strcmp(a, "--find-copies-harder")) - findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; - else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) { + o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) /* TODO: parse thresholds */ - findopts.flags |= GIT_DIFF_FIND_REWRITES; - } - else if (!check_uint16_param(a, "-U", &opts.context_lines) && - !check_uint16_param(a, "--unified=", &opts.context_lines) && - !check_uint16_param(a, "--inter-hunk-context=", - &opts.interhunk_lines) && - !check_str_param(a, "--src-prefix=", &opts.old_prefix) && - !check_str_param(a, "--dst-prefix=", &opts.new_prefix) && - !check_str_param(a, "--git-dir=", &dir)) - usage("Unknown arg", a); + o->findopts.flags |= GIT_DIFF_FIND_REWRITES; + else if (!match_uint16_arg( + &o->diffopts.context_lines, &args, "-U") && + !match_uint16_arg( + &o->diffopts.context_lines, &args, "--unified") && + !match_uint16_arg( + &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && + !match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") && + !match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") && + !match_str_arg(&o->dir, &args, "--git-dir")) + usage("Unknown command line argument", a); } - - /* open repo */ - - check(git_repository_open_ext(&repo, dir, 0, NULL), - "Could not open repository"); - - if (treeish1) - check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree"); - if (treeish2) - check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree"); - - /* */ - /* --cached */ - /* */ - /* --cached */ - /* nothing */ - - if (t1 && t2) - check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff"); - else if (t1 && cached) - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - else if (t1) { - git_diff_list *diff2; - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - check(git_diff_index_to_workdir(&diff2, repo, NULL, &opts), "Diff"); - check(git_diff_merge(diff, diff2), "Merge diffs"); - git_diff_list_free(diff2); - } - else if (cached) { - check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD"); - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - } - else - check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff"); - - if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0) - check(git_diff_find_similar(diff, &findopts), - "finding renames and copies "); - - if (color >= 0) - fputs(colors[0], stdout); - - switch (format) { - case FORMAT_PATCH: - check(git_diff_print_patch(diff, printer, &color), "Displaying diff"); - break; - case FORMAT_COMPACT: - check(git_diff_print_compact(diff, printer, &color), "Displaying diff"); - break; - case FORMAT_RAW: - check(git_diff_print_raw(diff, printer, &color), "Displaying diff"); - break; - } - - if (color >= 0) - fputs(colors[0], stdout); - - git_diff_list_free(diff); - git_tree_free(t1); - git_tree_free(t2); - git_repository_free(repo); - - git_threads_shutdown(); - - return 0; } - diff --git a/examples/general.c b/examples/general.c index d7a58479c..58e141b35 100644 --- a/examples/general.c +++ b/examples/general.c @@ -1,3 +1,17 @@ +/* + * libgit2 "general" example - shows basic libgit2 concepts + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + // [**libgit2**][lg] is a portable, pure C implementation of the Git core // methods provided as a re-entrant linkable library with a solid API, // allowing you to write native speed custom Git applications in any @@ -52,7 +66,7 @@ int main (int argc, char** argv) // simplest. There are also [methods][me] for specifying the index file // and work tree locations, here we assume they are in the normal places. // - // (Try running this program against tests-clar/resources/testrepo.git.) + // (Try running this program against tests/resources/testrepo.git.) // // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository int error; diff --git a/examples/init.c b/examples/init.c new file mode 100644 index 000000000..0e823ab81 --- /dev/null +++ b/examples/init.c @@ -0,0 +1,253 @@ +/* + * libgit2 "init" example - shows how to initialize a new repo + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** + * This is a sample program that is similar to "git init". See the + * documentation for that (try "git help init") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to initialize a new repository. + * + * This also contains a special additional option that regular "git init" + * does not support which is "--initial-commit" to make a first empty commit. + * That is demonstrated in the "create_initial_commit" helper function. + */ + +/** Forward declarations of helpers */ +struct opts { + int no_options; + int quiet; + int bare; + int initial_commit; + uint32_t shared; + const char *template; + const char *gitdir; + const char *dir; +}; +static void create_initial_commit(git_repository *repo); +static void parse_opts(struct opts *o, int argc, char *argv[]); + + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; + + git_threads_init(); + + parse_opts(&o, argc, argv); + + /* Initialize repository. */ + + if (o.no_options) { + /** + * No options were specified, so let's demonstrate the default + * simple case of git_repository_init() API usage... + */ + check_lg2(git_repository_init(&repo, o.dir, 0), + "Could not initialize repository", NULL); + } + else { + /** + * Some command line options were specified, so we'll use the + * extended init API to handle them + */ + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + initopts.flags = GIT_REPOSITORY_INIT_MKPATH; + + if (o.bare) + initopts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (o.template) { + initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + initopts.template_path = o.template; + } + + if (o.gitdir) { + /** + * If you specified a separate git directory, then initialize + * the repository at that path and use the second path as the + * working directory of the repository (with a git-link file) + */ + initopts.workdir_path = o.dir; + o.dir = o.gitdir; + } + + if (o.shared != 0) + initopts.mode = o.shared; + + check_lg2(git_repository_init_ext(&repo, o.dir, &initopts), + "Could not initialize repository", NULL); + } + + /** Print a message to stdout like "git init" does. */ + + if (!o.quiet) { + if (o.bare || o.gitdir) + o.dir = git_repository_path(repo); + else + o.dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", o.dir); + } + + /** + * As an extension to the basic "git init" command, this example + * gives the option to create an empty initial commit. This is + * mostly to demonstrate what it takes to do that, but also some + * people like to have that empty base commit in their repo. + */ + if (o.initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + git_threads_shutdown(); + + return 0; +} + +/** + * Unlike regular "git init", this example shows how to create an initial + * empty commit in the repository. This is the helper function that does + * that. + */ +static void create_initial_commit(git_repository *repo) +{ + git_signature *sig; + git_index *index; + git_oid tree_id, commit_id; + git_tree *tree; + + /** First use the config to initialize a commit signature for the user. */ + + if (git_signature_default(&sig, repo) < 0) + fatal("Unable to create a commit signature.", + "Perhaps 'user.name' and 'user.email' are not set"); + + /* Now let's create an empty tree for this commit */ + + if (git_repository_index(&index, repo) < 0) + fatal("Could not open repository index", NULL); + + /** + * Outside of this example, you could call git_index_add_bypath() + * here to put actual files into the index. For our purposes, we'll + * leave it empty for now. + */ + + if (git_index_write_tree(&tree_id, index) < 0) + fatal("Unable to write initial tree from index", NULL); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fatal("Could not look up initial tree", NULL); + + /** + * Ready to create the initial commit. + * + * Normally creating a commit would involve looking up the current + * HEAD commit and making that be the parent of the initial commit, + * but here this is the first commit so there will be no parent. + */ + + if (git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, "Initial commit", tree, 0) < 0) + fatal("Could not create the initial commit", NULL); + + /** Clean up so we don't leak memory. */ + + git_tree_free(tree); + git_signature_free(sig); +} + +static void usage(const char *error, const char *arg) +{ + fprintf(stderr, "error: %s '%s'\n", error, arg); + fprintf(stderr, + "usage: init [-q | --quiet] [--bare] [--template=]\n" + " [--shared[=perms]] [--initial-commit]\n" + " [--separate-git-dir] \n"); + exit(1); +} + +/** Parse the tail of the --shared= argument. */ +static uint32_t parse_shared(const char *shared) +{ + if (!strcmp(shared, "false") || !strcmp(shared, "umask")) + return GIT_REPOSITORY_INIT_SHARED_UMASK; + + else if (!strcmp(shared, "true") || !strcmp(shared, "group")) + return GIT_REPOSITORY_INIT_SHARED_GROUP; + + else if (!strcmp(shared, "all") || !strcmp(shared, "world") || + !strcmp(shared, "everybody")) + return GIT_REPOSITORY_INIT_SHARED_ALL; + + else if (shared[0] == '0') { + long val; + char *end = NULL; + val = strtol(shared + 1, &end, 8); + if (end == shared + 1 || *end != 0) + usage("invalid octal value for --shared", shared); + return (uint32_t)val; + } + + else + usage("unknown value for --shared", shared); + + return 0; +} + +static void parse_opts(struct opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + const char *sharedarg; + + /** Process arguments. */ + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] == '-') + o->no_options = 0; + + if (a[0] != '-') { + if (o->dir != NULL) + usage("extra argument", a); + o->dir = a; + } + else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) + o->quiet = 1; + else if (!strcmp(a, "--bare")) + o->bare = 1; + else if (!strcmp(a, "--shared")) + o->shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if (!strcmp(a, "--initial-commit")) + o->initial_commit = 1; + else if (match_str_arg(&sharedarg, &args, "--shared")) + o->shared = parse_shared(sharedarg); + else if (!match_str_arg(&o->template, &args, "--template") || + !match_str_arg(&o->gitdir, &args, "--separate-git-dir")) + usage("unknown option", a); + } + + if (!o->dir) + usage("must specify directory to init", NULL); +} diff --git a/examples/log.c b/examples/log.c new file mode 100644 index 000000000..471c5ff96 --- /dev/null +++ b/examples/log.c @@ -0,0 +1,434 @@ +/* + * libgit2 "log" example - shows how to walk history and get commit info + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** + * This example demonstrates the libgit2 rev walker APIs to roughly + * simulate the output of `git log` and a few of command line arguments. + * `git log` has many many options and this only shows a few of them. + * + * This does not have: + * + * - Robust error handling + * - Colorized or paginated output formatting + * - Most of the `git log` options + * + * This does have: + * + * - Examples of translating command line arguments to equivalent libgit2 + * revwalker configuration calls + * - Simplified options to apply pathspec limits and to show basic diffs + */ + +/** log_state represents walker being configured while handling options */ +struct log_state { + git_repository *repo; + const char *repodir; + git_revwalk *walker; + int hide; + int sorting; + int revisions; +}; + +/** utility functions that are called to configure the walker */ +static void set_sorting(struct log_state *s, unsigned int sort_mode); +static void push_rev(struct log_state *s, git_object *obj, int hide); +static int add_revision(struct log_state *s, const char *revstr); + +/** log_options holds other command line options that affect log output */ +struct log_options { + int show_diff; + int skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + char *author; + char *committer; +}; + +/** utility functions that parse options and help with log output */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv); +static void print_time(const git_time *intime, const char *prefix); +static void print_commit(git_commit *commit); +static int match_with_parent(git_commit *commit, int i, git_diff_options *); + + +int main(int argc, char *argv[]) +{ + int i, count = 0, printed = 0, parents, last_arg; + struct log_state s; + struct log_options opt; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_oid oid; + git_commit *commit = NULL; + git_pathspec *ps = NULL; + + git_threads_init(); + + /** Parse arguments and set up revwalker. */ + + last_arg = parse_options(&s, &opt, argc, argv); + + diffopts.pathspec.strings = &argv[last_arg]; + diffopts.pathspec.count = argc - last_arg; + if (diffopts.pathspec.count > 0) + check_lg2(git_pathspec_new(&ps, &diffopts.pathspec), + "Building pathspec", NULL); + + if (!s.revisions) + add_revision(&s, NULL); + + /** Use the revwalker to traverse the history. */ + + printed = count = 0; + + for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { + check_lg2(git_commit_lookup(&commit, s.repo, &oid), + "Failed to look up commit", NULL); + + parents = (int)git_commit_parentcount(commit); + if (parents < opt.min_parents) + continue; + if (opt.max_parents > 0 && parents > opt.max_parents) + continue; + + if (diffopts.pathspec.count > 0) { + int unmatched = parents; + + if (parents == 0) { + git_tree *tree; + check_lg2(git_commit_tree(&tree, commit), "Get tree", NULL); + if (git_pathspec_match_tree( + NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) + unmatched = 1; + git_tree_free(tree); + } else if (parents == 1) { + unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1; + } else { + for (i = 0; i < parents; ++i) { + if (match_with_parent(commit, i, &diffopts)) + unmatched--; + } + } + + if (unmatched > 0) + continue; + } + + if (count++ < opt.skip) + continue; + if (opt.limit != -1 && printed++ >= opt.limit) { + git_commit_free(commit); + break; + } + + print_commit(commit); + + if (opt.show_diff) { + git_tree *a = NULL, *b = NULL; + git_diff *diff = NULL; + + if (parents > 1) + continue; + check_lg2(git_commit_tree(&b, commit), "Get tree", NULL); + if (parents == 1) { + git_commit *parent; + check_lg2(git_commit_parent(&parent, commit, 0), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + git_commit_free(parent); + } + + check_lg2(git_diff_tree_to_tree( + &diff, git_commit_owner(commit), a, b, &diffopts), + "Diff commit with parent", NULL); + check_lg2( + git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, diff_output, NULL), + "Displaying diff", NULL); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + } + } + + git_pathspec_free(ps); + git_revwalk_free(s.walker); + git_repository_free(s.repo); + git_threads_shutdown(); + + return 0; +} + +/** Push object (for hide or show) onto revwalker. */ +static void push_rev(struct log_state *s, git_object *obj, int hide) +{ + hide = s->hide ^ hide; + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) { + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + git_revwalk_sorting(s->walker, s->sorting); + } + + if (!obj) + check_lg2(git_revwalk_push_head(s->walker), + "Could not find repository HEAD", NULL); + else if (hide) + check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + else + check_lg2(git_revwalk_push(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + + git_object_free(obj); +} + +/** Parse revision string and add revs to walker. */ +static int add_revision(struct log_state *s, const char *revstr) +{ + git_revspec revs; + int hide = 0; + + /** Open repo on demand if it isn't already open. */ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + if (!revstr) { + push_rev(s, NULL, hide); + return 0; + } + + if (*revstr == '^') { + revs.flags = GIT_REVPARSE_SINGLE; + hide = !hide; + + if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0) + return -1; + } else if (git_revparse(&revs, s->repo, revstr) < 0) + return -1; + + if ((revs.flags & GIT_REVPARSE_SINGLE) != 0) + push_rev(s, revs.from, hide); + else { + push_rev(s, revs.to, hide); + + if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check_lg2(git_merge_base(&base, s->repo, + git_object_id(revs.from), git_object_id(revs.to)), + "Could not find merge base", revstr); + check_lg2( + git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT), + "Could not find merge base commit", NULL); + + push_rev(s, revs.to, hide); + } + + push_rev(s, revs.from, !hide); + } + + return 0; +} + +/** Update revwalker with sorting mode. */ +static void set_sorting(struct log_state *s, unsigned int sort_mode) +{ + /** Open repo on demand if it isn't already open. */ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + + if (sort_mode == GIT_SORT_REVERSE) + s->sorting = s->sorting ^ GIT_SORT_REVERSE; + else + s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + + git_revwalk_sorting(s->walker, s->sorting); +} + +/** Helper to format a git_time value like Git. */ +static void print_time(const git_time *intime, const char *prefix) +{ + char sign, out[32]; + struct tm *intm; + int offset, hours, minutes; + time_t t; + + offset = intime->offset; + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + hours = offset / 60; + minutes = offset % 60; + + t = (time_t)intime->time + (intime->offset * 60); + + intm = gmtime(&t); + strftime(out, sizeof(out), "%a %b %e %T %Y", intm); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + +/** Helper to print a commit object. */ +static void print_commit(git_commit *commit) +{ + char buf[GIT_OID_HEXSZ + 1]; + int i, count; + const git_signature *sig; + const char *scan, *eol; + + git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); + printf("commit %s\n", buf); + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); + } + + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (scan = git_commit_message(commit); scan && *scan; ) { + for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : NULL; + } + printf("\n"); +} + +/** Helper to find how many files in a commit changed from its nth parent. */ +static int match_with_parent(git_commit *commit, int i, git_diff_options *opts) +{ + git_commit *parent; + git_tree *a, *b; + git_diff *diff; + int ndeltas; + + check_lg2( + git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL); + check_lg2( + git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts), + "Checking diff between parent and commit", NULL); + + ndeltas = (int)git_diff_num_deltas(diff); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + git_commit_free(parent); + + return ndeltas > 0; +} + +/** Print a usage message for the program. */ +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: log []\n"); + exit(1); +} + +/** Parse some log command line options. */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + memset(s, 0, sizeof(*s)); + s->sorting = GIT_SORT_TIME; + + memset(opt, 0, sizeof(*opt)); + opt->max_parents = -1; + opt->limit = -1; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (!add_revision(s, a)) + s->revisions++; + else + /** Try failed revision parse as filename. */ + break; + } else if (!strcmp(a, "--")) { + ++args.pos; + break; + } + else if (!strcmp(a, "--date-order")) + set_sorting(s, GIT_SORT_TIME); + else if (!strcmp(a, "--topo-order")) + set_sorting(s, GIT_SORT_TOPOLOGICAL); + else if (!strcmp(a, "--reverse")) + set_sorting(s, GIT_SORT_REVERSE); + else if (match_str_arg(&s->repodir, &args, "--git-dir")) + /** Found git-dir. */; + else if (match_int_arg(&opt->skip, &args, "--skip", 0)) + /** Found valid --skip. */; + else if (match_int_arg(&opt->limit, &args, "--max-count", 0)) + /** Found valid --max-count. */; + else if (a[1] >= '0' && a[1] <= '9') + is_integer(&opt->limit, a + 1, 0); + else if (match_int_arg(&opt->limit, &args, "-n", 0)) + /** Found valid -n. */; + else if (!strcmp(a, "--merges")) + opt->min_parents = 2; + else if (!strcmp(a, "--no-merges")) + opt->max_parents = 1; + else if (!strcmp(a, "--no-min-parents")) + opt->min_parents = 0; + else if (!strcmp(a, "--no-max-parents")) + opt->max_parents = -1; + else if (match_int_arg(&opt->max_parents, &args, "--max-parents=", 1)) + /** Found valid --max-parents. */; + else if (match_int_arg(&opt->min_parents, &args, "--min-parents=", 0)) + /** Found valid --min_parents. */; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + opt->show_diff = 1; + else + usage("Unsupported argument", a); + } + + return args.pos; +} + diff --git a/examples/network/Makefile b/examples/network/Makefile index 810eb705b..f65c6cb26 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -11,7 +11,8 @@ OBJECTS = \ ls-remote.o \ fetch.o \ clone.o \ - index-pack.o + index-pack.o \ + common.o all: $(OBJECTS) $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES) diff --git a/examples/network/clone.c b/examples/network/clone.c index 00c25c1ae..4df47eb7f 100644 --- a/examples/network/clone.c +++ b/examples/network/clone.c @@ -9,19 +9,6 @@ # include #endif -/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ - * with permission of the original author, Martin Pool. - * http://sourcefrog.net/weblog/software/languages/C/unused.html - */ -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) -#elif defined(__LCLINT__) -# define UNUSED(x) /*@unused@*/ x -#else -# define UNUSED(x) x -#endif - typedef struct progress_data { git_transfer_progress fetch_progress; size_t completed_steps; @@ -38,13 +25,19 @@ static void print_progress(const progress_data *pd) : 0.f; int kbytes = pd->fetch_progress.received_bytes / 1024; - printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n", + if (pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) { + printf("Resolving deltas %d/%d\r", + pd->fetch_progress.indexed_deltas, + pd->fetch_progress.total_deltas); + } else { + printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n", network_percent, kbytes, pd->fetch_progress.received_objects, pd->fetch_progress.total_objects, index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects, checkout_percent, pd->completed_steps, pd->total_steps, pd->path); + } } static int fetch_progress(const git_transfer_progress *stats, void *payload) @@ -63,24 +56,6 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa print_progress(pd); } -static int cred_acquire(git_cred **out, - const char * UNUSED(url), - const char * UNUSED(username_from_url), - unsigned int UNUSED(allowed_types), - void * UNUSED(payload)) -{ - char username[128] = {0}; - char password[128] = {0}; - - printf("Username: "); - scanf("%s", username); - - /* Yup. Right there on your terminal. Careful where you copy/paste output. */ - printf("Password: "); - scanf("%s", password); - - return git_cred_userpass_plaintext_new(out, username, password); -} int do_clone(git_repository *repo, int argc, char **argv) { @@ -105,9 +80,9 @@ int do_clone(git_repository *repo, int argc, char **argv) checkout_opts.progress_cb = checkout_progress; checkout_opts.progress_payload = &pd; clone_opts.checkout_opts = checkout_opts; - clone_opts.fetch_progress_cb = &fetch_progress; - clone_opts.fetch_progress_payload = &pd; - clone_opts.cred_acquire_cb = cred_acquire; + clone_opts.remote_callbacks.transfer_progress = &fetch_progress; + clone_opts.remote_callbacks.credentials = cred_acquire_cb; + clone_opts.remote_callbacks.payload = &pd; // Do the clone error = git_clone(&cloned_repo, url, path, &clone_opts); diff --git a/examples/network/common.c b/examples/network/common.c new file mode 100644 index 000000000..d123eedbd --- /dev/null +++ b/examples/network/common.c @@ -0,0 +1,34 @@ +#include "common.h" +#include + +/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ + * with permission of the original author, Martin Pool. + * http://sourcefrog.net/weblog/software/languages/C/unused.html + */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +int cred_acquire_cb(git_cred **out, + const char * UNUSED(url), + const char * UNUSED(username_from_url), + unsigned int UNUSED(allowed_types), + void * UNUSED(payload)) +{ + char username[128] = {0}; + char password[128] = {0}; + + printf("Username: "); + scanf("%s", username); + + /* Yup. Right there on your terminal. Careful where you copy/paste output. */ + printf("Password: "); + scanf("%s", password); + + return git_cred_userpass_plaintext_new(out, username, password); +} diff --git a/examples/network/common.h b/examples/network/common.h index a4cfa1a7e..1b09caad4 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -12,6 +12,12 @@ int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); int do_clone(git_repository *repo, int argc, char **argv); +int cred_acquire_cb(git_cred **out, + const char * url, + const char * username_from_url, + unsigned int allowed_types, + void *payload); + #ifndef PRIuZ /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 6020ec6ec..474b45bbb 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -14,11 +14,12 @@ struct dl_data { int finished; }; -static void progress_cb(const char *str, int len, void *data) +static int progress_cb(const char *str, int len, void *data) { (void)data; printf("remote: %.*s", len, str); fflush(stdout); /* We don't have the \n to force the flush */ + return 0; } static void *download(void *ptr) @@ -35,7 +36,7 @@ static void *download(void *ptr) // 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, NULL) < 0) { + if (git_remote_download(data->remote) < 0) { data->ret = -1; goto exit; } @@ -47,6 +48,11 @@ exit: 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 + * an update. + */ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) { char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; @@ -66,6 +72,7 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo return 0; } +/** Entry point for this command */ int fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; @@ -91,6 +98,7 @@ int fetch(git_repository *repo, int argc, char **argv) // Set up the callbacks (only update_tips for now) callbacks.update_tips = &update_cb; callbacks.progress = &progress_cb; + callbacks.credentials = cred_acquire_cb; git_remote_set_callbacks(remote, &callbacks); // Set up the information for the background worker thread @@ -112,10 +120,14 @@ int fetch(git_repository *repo, int argc, char **argv) do { usleep(10000); - if (stats->total_objects > 0) + 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) @@ -124,8 +136,18 @@ int fetch(git_repository *repo, int argc, char **argv) pthread_join(worker, NULL); #endif - printf("\rReceived %d/%d objects in %zu bytes\n", + /** + * 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. + */ + if (stats->local_objects > 0) { + printf("\rReceived %d/%d objects in %zu bytes (used %d local objects)\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); + } else{ + printf("\rReceived %d/%d objects in %zu bytes\n", stats->indexed_objects, stats->total_objects, stats->received_bytes); + } // Disconnect the underlying connection to prevent from idling. git_remote_disconnect(remote); diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c index 889305da8..314f21160 100644 --- a/examples/network/index-pack.c +++ b/examples/network/index-pack.c @@ -31,7 +31,7 @@ static int index_cb(const git_transfer_progress *stats, void *data) int index_pack(git_repository *repo, int argc, char **argv) { - git_indexer_stream *idx; + git_indexer *idx; git_transfer_progress stats = {0, 0}; int error; char hash[GIT_OID_HEXSZ + 1] = {0}; @@ -46,7 +46,7 @@ int index_pack(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - if (git_indexer_stream_new(&idx, ".", NULL, NULL) < 0) { + if (git_indexer_new(&idx, ".", 0, NULL, NULL, NULL) < 0) { puts("bad idx"); return -1; } @@ -61,7 +61,7 @@ int index_pack(git_repository *repo, int argc, char **argv) if (read_bytes < 0) break; - if ((error = git_indexer_stream_add(idx, buf, read_bytes, &stats)) < 0) + if ((error = git_indexer_append(idx, buf, read_bytes, &stats)) < 0) goto cleanup; index_cb(&stats, NULL); @@ -73,16 +73,16 @@ int index_pack(git_repository *repo, int argc, char **argv) goto cleanup; } - if ((error = git_indexer_stream_finalize(idx, &stats)) < 0) + if ((error = git_indexer_commit(idx, &stats)) < 0) goto cleanup; printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects); - git_oid_fmt(hash, git_indexer_stream_hash(idx)); + git_oid_fmt(hash, git_indexer_hash(idx)); puts(hash); cleanup: close(fd); - git_indexer_stream_free(idx); + git_indexer_free(idx); return error; } diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 252011828..1e08b293e 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -4,65 +4,52 @@ #include #include "common.h" -static int show_ref__cb(git_remote_head *head, void *payload) -{ - char oid[GIT_OID_HEXSZ + 1] = {0}; - - (void)payload; - git_oid_fmt(oid, &head->oid); - printf("%s\t%s\n", oid, head->name); - return 0; -} - -static int use_unnamed(git_repository *repo, const char *url) -{ - git_remote *remote = NULL; - int error; - - // Create an instance of a remote from the URL. The transport to use - // is detected from the URL - error = git_remote_create_inmemory(&remote, repo, NULL, url); - if (error < 0) - goto cleanup; - - // When connecting, the underlying code needs to know wether we - // want to push or fetch - error = git_remote_connect(remote, GIT_DIRECTION_FETCH); - if (error < 0) - goto cleanup; - - // With git_remote_ls we can retrieve the advertised heads - error = git_remote_ls(remote, &show_ref__cb, NULL); - -cleanup: - git_remote_free(remote); - return error; -} - static int use_remote(git_repository *repo, char *name) { git_remote *remote = NULL; int error; + const git_remote_head **refs; + size_t refs_len, i; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; // Find the remote by name error = git_remote_load(&remote, repo, name); - if (error < 0) - goto cleanup; + if (error < 0) { + error = git_remote_create_inmemory(&remote, repo, NULL, name); + if (error < 0) + goto cleanup; + } + + /** + * Connect to the remote and call the printing function for + * each of the remote references. + */ + callbacks.credentials = cred_acquire_cb; + git_remote_set_callbacks(remote, &callbacks); error = git_remote_connect(remote, GIT_DIRECTION_FETCH); if (error < 0) goto cleanup; - error = git_remote_ls(remote, &show_ref__cb, NULL); + /** + * Get the list of references on the remote and print out + * their name next to what they point to. + */ + if (git_remote_ls(&refs, &refs_len, remote) < 0) + goto cleanup; + + for (i = 0; i < refs_len; i++) { + char oid[GIT_OID_HEXSZ + 1] = {0}; + git_oid_fmt(oid, &refs[i]->oid); + printf("%s\t%s\n", oid, refs[i]->name); + } cleanup: git_remote_free(remote); return error; } -// This gets called to do the work. The remote can be given either as -// the name of a configured remote or an URL. - +/** Entry point for this command */ int ls_remote(git_repository *repo, int argc, char **argv) { int error; @@ -72,12 +59,7 @@ int ls_remote(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - /* If there's a ':' in the name, assume it's an URL */ - if (strchr(argv[1], ':') != NULL) { - error = use_unnamed(repo, argv[1]); - } else { - error = use_remote(repo, argv[1]); - } + error = use_remote(repo, argv[1]); return error; } diff --git a/examples/rev-list.c b/examples/rev-list.c index 1fb7ebf9f..5c0d751a3 100644 --- a/examples/rev-list.c +++ b/examples/rev-list.c @@ -1,17 +1,43 @@ -#include -#include +/* + * libgit2 "rev-list" example - shows how to transform a rev-spec into a list + * of commit ids + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ -#include +#include "common.h" -static void check_error(int error_code, const char *action) +static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts); + +int main (int argc, char **argv) { - if (!error_code) - return; + git_repository *repo; + git_revwalk *walk; + git_oid oid; + char buf[41]; - const git_error *error = giterr_last(); - fprintf(stderr, "Error %d %s: %s\n", -error_code, action, - (error && error->message) ? error->message : "???"); - exit(1); + git_threads_init(); + + check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "opening repository", NULL); + check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL); + check_lg2(revwalk_parseopts(repo, walk, argc-1, argv+1), "parsing options", NULL); + + while (!git_revwalk_next(&oid, walk)) { + git_oid_fmt(buf, &oid); + buf[40] = '\0'; + printf("%s\n", buf); + } + + git_threads_shutdown(); + return 0; } static int push_commit(git_revwalk *walk, const git_oid *oid, int hide) @@ -93,27 +119,3 @@ static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, return 0; } -int main (int argc, char **argv) -{ - int error; - git_repository *repo; - git_revwalk *walk; - git_oid oid; - char buf[41]; - - error = git_repository_open_ext(&repo, ".", 0, NULL); - check_error(error, "opening repository"); - - error = git_revwalk_new(&walk, repo); - check_error(error, "allocating revwalk"); - error = revwalk_parseopts(repo, walk, argc-1, argv+1); - check_error(error, "parsing options"); - - while (!git_revwalk_next(&oid, walk)) { - git_oid_fmt(buf, &oid); - buf[40] = '\0'; - printf("%s\n", buf); - } - - return 0; -} diff --git a/examples/rev-parse.c b/examples/rev-parse.c new file mode 100644 index 000000000..a24833767 --- /dev/null +++ b/examples/rev-parse.c @@ -0,0 +1,115 @@ +/* + * libgit2 "rev-parse" example - shows how to parse revspecs + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** Forward declarations for helpers. */ +struct parse_state { + git_repository *repo; + const char *repodir; + const char *spec; + int not; +}; +static void parse_opts(struct parse_state *ps, int argc, char *argv[]); +static int parse_revision(struct parse_state *ps); + + +int main(int argc, char *argv[]) +{ + struct parse_state ps = {0}; + + git_threads_init(); + parse_opts(&ps, argc, argv); + + check_lg2(parse_revision(&ps), "Parsing", NULL); + + git_repository_free(ps.repo); + git_threads_shutdown(); + + return 0; +} + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: rev-parse [ --option ] ...\n"); + exit(1); +} + +static void parse_opts(struct parse_state *ps, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos=1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (ps->spec) + usage("Too many specs", a); + ps->spec = a; + } else if (!strcmp(a, "--not")) + ps->not = !ps->not; + else if (!match_str_arg(&ps->repodir, &args, "--git-dir")) + usage("Cannot handle argument", a); + } +} + +static int parse_revision(struct parse_state *ps) +{ + git_revspec rs; + char str[GIT_OID_HEXSZ + 1]; + + if (!ps->repo) { + if (!ps->repodir) + ps->repodir = "."; + check_lg2(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL), + "Could not open repository from", ps->repodir); + } + + check_lg2(git_revparse(&rs, ps->repo, ps->spec), "Could not parse", ps->spec); + + if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("%s\n", str); + git_object_free(rs.from); + } + else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.to)); + printf("%s\n", str); + git_object_free(rs.to); + + if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check_lg2(git_merge_base(&base, ps->repo, + git_object_id(rs.from), git_object_id(rs.to)), + "Could not find merge base", ps->spec); + + git_oid_tostr(str, sizeof(str), &base); + printf("%s\n", str); + } + + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("^%s\n", str); + git_object_free(rs.from); + } + else { + fatal("Invalid results from git_revparse", ps->spec); + } + + return 0; +} + diff --git a/examples/showindex.c b/examples/showindex.c index e92a9c8de..7f7c38379 100644 --- a/examples/showindex.c +++ b/examples/showindex.c @@ -1,10 +1,21 @@ -#include -#include -#include +/* + * libgit2 "showindex" example - shows how to extract data from the index + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" int main (int argc, char** argv) { - git_repository *repo = NULL; git_index *index; unsigned int i, ecount; char *dir = "."; @@ -12,31 +23,24 @@ int main (int argc, char** argv) char out[41]; out[40] = '\0'; + git_threads_init(); + + if (argc > 2) + fatal("usage: showindex []", NULL); if (argc > 1) dir = argv[1]; - if (!dir || argc > 2) { - fprintf(stderr, "usage: showindex []\n"); - return 1; - } dirlen = strlen(dir); if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) { - if (git_index_open(&index, dir) < 0) { - fprintf(stderr, "could not open index: %s\n", dir); - return 1; - } + check_lg2(git_index_open(&index, dir), "could not open index", dir); } else { - if (git_repository_open_ext(&repo, dir, 0, NULL) < 0) { - fprintf(stderr, "could not open repository: %s\n", dir); - return 1; - } - if (git_repository_index(&index, repo) < 0) { - fprintf(stderr, "could not open repository index\n"); - return 1; - } + git_repository *repo; + check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir); + check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL); + git_repository_free(repo); } - git_index_read(index); + git_index_read(index, 0); ecount = git_index_entrycount(index); if (!ecount) @@ -60,8 +64,7 @@ int main (int argc, char** argv) } git_index_free(index); - git_repository_free(repo); + git_threads_shutdown(); return 0; } - diff --git a/examples/status.c b/examples/status.c index 689098415..3adfe0d5d 100644 --- a/examples/status.c +++ b/examples/status.c @@ -1,33 +1,32 @@ /* - * Copyright (C) the libgit2 contributors. All rights reserved. + * libgit2 "status" example - shows how to use the status APIs * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . */ -#include -#include -#include -#include -enum { - FORMAT_DEFAULT = 0, - FORMAT_LONG = 1, - FORMAT_SHORT = 2, - FORMAT_PORCELAIN = 3, -}; -#define MAX_PATHSPEC 8 +#include "common.h" -/* +/** * This example demonstrates the use of the libgit2 status APIs, * particularly the `git_status_list` object, to roughly simulate the * output of running `git status`. It serves as a simple example of * using those APIs to get basic status information. * * This does not have: + * * - Robust error handling * - Colorized or paginated output formatting * * This does have: + * * - Examples of translating command line arguments to the status * options settings to mimic `git status` results. * - A sample status formatter that matches the default "long" format @@ -35,34 +34,91 @@ enum { * - A sample status formatter that matches the "short" format */ -static void check(int error, const char *message, const char *extra) +enum { + FORMAT_DEFAULT = 0, + FORMAT_LONG = 1, + FORMAT_SHORT = 2, + FORMAT_PORCELAIN = 3, +}; + +#define MAX_PATHSPEC 8 + +struct opts { + git_status_options statusopt; + char *repodir; + char *pathspec[MAX_PATHSPEC]; + int npaths; + int format; + int zterm; + int showbranch; +}; + +static void parse_opts(struct opts *o, int argc, char *argv[]); +static void show_branch(git_repository *repo, int format); +static void print_long(git_status_list *status); +static void print_short(git_repository *repo, git_status_list *status); + +int main(int argc, char *argv[]) { - const git_error *lg2err; - const char *lg2msg = "", *lg2spacer = ""; + git_repository *repo = NULL; + git_status_list *status; + struct opts o = { GIT_STATUS_OPTIONS_INIT, "." }; - if (!error) - return; + git_threads_init(); - if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { - lg2msg = lg2err->message; - lg2spacer = " - "; - } + o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; - if (extra) - fprintf(stderr, "%s '%s' [%d]%s%s\n", - message, extra, error, lg2spacer, lg2msg); + parse_opts(&o, argc, argv); + + /** + * Try to open the repository at the given path (or at the current + * directory if none was given). + */ + check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL), + "Could not open repository", o.repodir); + + if (git_repository_is_bare(repo)) + fatal("Cannot report status on bare repository", + git_repository_path(repo)); + + /** + * Run status on the repository + * + * We use `git_status_list_new()` to generate a list of status + * information which lets us iterate over it at our + * convenience and extract the data we want to show out of + * each entry. + * + * You can use `git_status_foreach()` or + * `git_status_foreach_ext()` if you'd prefer to execute a + * callback for each entry. The latter gives you more control + * about what results are presented. + */ + check_lg2(git_status_list_new(&status, repo, &o.statusopt), + "Could not get status", NULL); + + if (o.showbranch) + show_branch(repo, o.format); + + if (o.format == FORMAT_LONG) + print_long(status); else - fprintf(stderr, "%s [%d]%s%s\n", - message, error, lg2spacer, lg2msg); + print_short(repo, status); - exit(1); -} - -static void fail(const char *message) -{ - check(-1, message, NULL); + git_status_list_free(status); + git_repository_free(repo); + git_threads_shutdown(); + + return 0; } +/** + * If the user asked for the branch, let's show the short name of the + * branch. + */ static void show_branch(git_repository *repo, int format) { int error = 0; @@ -71,14 +127,12 @@ static void show_branch(git_repository *repo, int format) error = git_repository_head(&head, repo); - if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND) + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) branch = NULL; else if (!error) { - branch = git_reference_name(head); - if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) - branch += strlen("refs/heads/"); + branch = git_reference_shorthand(head); } else - check(error, "failed to get current branch", NULL); + check_lg2(error, "failed to get current branch", NULL); if (format == FORMAT_LONG) printf("# On branch %s\n", @@ -89,7 +143,11 @@ static void show_branch(git_repository *repo, int format) git_reference_free(head); } -static void print_long(git_repository *repo, git_status_list *status) +/** + * This function print out an output similar to git's status command + * in long form, including the command-line hints. + */ +static void print_long(git_status_list *status) { size_t i, maxi = git_status_list_entrycount(status); const git_status_entry *s; @@ -97,9 +155,7 @@ static void print_long(git_repository *repo, git_status_list *status) int changed_in_workdir = 0, rm_in_workdir = 0; const char *old_path, *new_path; - (void)repo; - - /* print index changes */ + /** Print index changes. */ for (i = 0; i < maxi; ++i) { char *istatus = NULL; @@ -148,16 +204,22 @@ static void print_long(git_repository *repo, git_status_list *status) } header = 0; - /* print workdir changes to tracked files */ + /** Print workdir changes to tracked files. */ for (i = 0; i < maxi; ++i) { char *wstatus = NULL; s = git_status_byindex(status, i); + /** + * With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example) + * `index_to_workdir` may not be `NULL` even if there are + * no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`. + */ if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL) continue; + /** Print out the output since we know the file has some changes */ if (s->status & GIT_STATUS_WT_MODIFIED) wstatus = "modified: "; if (s->status & GIT_STATUS_WT_DELETED) @@ -191,9 +253,8 @@ static void print_long(git_repository *repo, git_status_list *status) changed_in_workdir = 1; printf("#\n"); } - header = 0; - /* print untracked files */ + /** Print untracked files. */ header = 0; @@ -215,7 +276,7 @@ static void print_long(git_repository *repo, git_status_list *status) header = 0; - /* print ignored files */ + /** Print ignored files. */ for (i = 0; i < maxi; ++i) { s = git_status_byindex(status, i); @@ -237,6 +298,10 @@ static void print_long(git_repository *repo, git_status_list *status) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); } +/** + * This version of the output prefixes each path with two status + * columns and shows submodule status information. + */ static void print_short(git_repository *repo, git_status_list *status) { size_t i, maxi = git_status_list_entrycount(status); @@ -287,6 +352,10 @@ static void print_short(git_repository *repo, git_status_list *status) if (istatus == '?' && wstatus == '?') continue; + /** + * A commit in a tree is how submodules are stored, so + * let's go take a look at its status. + */ if (s->index_to_workdir && s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) { @@ -308,6 +377,10 @@ static void print_short(git_repository *repo, git_status_list *status) } } + /** + * Now that we have all the information, it's time to format the output. + */ + if (s->head_to_index) { a = s->head_to_index->old_file.path; b = s->head_to_index->new_file.path; @@ -341,103 +414,61 @@ static void print_short(git_repository *repo, git_status_list *status) } } -int main(int argc, char *argv[]) +/** + * Parse options that git's status command supports. + */ +static void parse_opts(struct opts *o, int argc, char *argv[]) { - git_repository *repo = NULL; - int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0; - git_status_options opt = GIT_STATUS_OPTIONS_INIT; - git_status_list *status; - char *repodir = ".", *pathspec[MAX_PATHSPEC]; + struct args_info args = ARGS_INFO_INIT; - opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; - for (i = 1; i < argc; ++i) { - if (argv[i][0] != '-') { - if (npaths < MAX_PATHSPEC) - pathspec[npaths++] = argv[i]; + if (a[0] != '-') { + if (o->npaths < MAX_PATHSPEC) + o->pathspec[o->npaths++] = a; else - fail("Example only supports a limited pathspec"); + fatal("Example only supports a limited pathspec", NULL); } - else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) - format = FORMAT_SHORT; - else if (!strcmp(argv[i], "--long")) - format = FORMAT_LONG; - else if (!strcmp(argv[i], "--porcelain")) - format = FORMAT_PORCELAIN; - else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) - showbranch = 1; - else if (!strcmp(argv[i], "-z")) { - zterm = 1; - if (format == FORMAT_DEFAULT) - format = FORMAT_PORCELAIN; + else if (!strcmp(a, "-s") || !strcmp(a, "--short")) + o->format = FORMAT_SHORT; + else if (!strcmp(a, "--long")) + o->format = FORMAT_LONG; + else if (!strcmp(a, "--porcelain")) + o->format = FORMAT_PORCELAIN; + else if (!strcmp(a, "-b") || !strcmp(a, "--branch")) + o->showbranch = 1; + else if (!strcmp(a, "-z")) { + o->zterm = 1; + if (o->format == FORMAT_DEFAULT) + o->format = FORMAT_PORCELAIN; } - else if (!strcmp(argv[i], "--ignored")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; - else if (!strcmp(argv[i], "-uno") || - !strcmp(argv[i], "--untracked-files=no")) - opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-unormal") || - !strcmp(argv[i], "--untracked-files=normal")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-uall") || - !strcmp(argv[i], "--untracked-files=all")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + else if (!strcmp(a, "--ignored")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; + else if (!strcmp(a, "-uno") || + !strcmp(a, "--untracked-files=no")) + o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(a, "-unormal") || + !strcmp(a, "--untracked-files=normal")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(a, "-uall") || + !strcmp(a, "--untracked-files=all")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - else if (!strcmp(argv[i], "--ignore-submodules=all")) - opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) - repodir = argv[i] + strlen("--git-dir="); + else if (!strcmp(a, "--ignore-submodules=all")) + o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) + o->repodir = a + strlen("--git-dir="); else - check(-1, "Unsupported option", argv[i]); + check_lg2(-1, "Unsupported option", a); } - if (format == FORMAT_DEFAULT) - format = FORMAT_LONG; - if (format == FORMAT_LONG) - showbranch = 1; - if (npaths > 0) { - opt.pathspec.strings = pathspec; - opt.pathspec.count = npaths; + if (o->format == FORMAT_DEFAULT) + o->format = FORMAT_LONG; + if (o->format == FORMAT_LONG) + o->showbranch = 1; + if (o->npaths > 0) { + o->statusopt.pathspec.strings = o->pathspec; + o->statusopt.pathspec.count = o->npaths; } - - /* - * Try to open the repository at the given path (or at the current - * directory if none was given). - */ - check(git_repository_open_ext(&repo, repodir, 0, NULL), - "Could not open repository", repodir); - - if (git_repository_is_bare(repo)) - fail("Cannot report status on bare repository"); - - /* - * Run status on the repository - * - * Because we want to simluate a full "git status" run and want to - * support some command line options, we use `git_status_foreach_ext()` - * instead of just the plain status call. This allows (a) iterating - * over the index and then the workdir and (b) extra flags that control - * which files are included. If you just want simple status (e.g. to - * enumerate files that are modified) then you probably don't need the - * extended API. - */ - check(git_status_list_new(&status, repo, &opt), - "Could not get status", NULL); - - if (showbranch) - show_branch(repo, format); - - if (format == FORMAT_LONG) - print_long(repo, status); - else - print_short(repo, status); - - git_status_list_free(status); - git_repository_free(repo); - - return 0; } - diff --git a/examples/test/test-rev-list.sh b/examples/test/test-rev-list.sh index bc0eea7cf..aa645be5e 100755 --- a/examples/test/test-rev-list.sh +++ b/examples/test/test-rev-list.sh @@ -4,7 +4,7 @@ THIS_FILE="$(readlink -f "$0")" ROOT="$(dirname "$(dirname "$(dirname "$THIS_FILE")")")" PROGRAM="$ROOT"/examples/rev-list LIBDIR="$ROOT"/build -REPO="$ROOT"/tests-clar/resources/testrepo.git +REPO="$ROOT"/tests/resources/testrepo.git cd "$REPO" diff --git a/git.git-authors b/git.git-authors index 3fcf27ffc..7d4c95bb3 100644 --- a/git.git-authors +++ b/git.git-authors @@ -68,3 +68,4 @@ ok Sebastian Schuberth ok Shawn O. Pearce ok Steffen Prohaska ok Sven Verdoolaege +ok Torsten Bögershausen diff --git a/include/git2.h b/include/git2.h index 5f9fc4824..d00a20a2e 100644 --- a/include/git2.h +++ b/include/git2.h @@ -8,53 +8,52 @@ #ifndef INCLUDE_git_git_h__ #define INCLUDE_git_git_h__ +#include "git2/attr.h" +#include "git2/blob.h" +#include "git2/blame.h" +#include "git2/branch.h" +#include "git2/buffer.h" +#include "git2/checkout.h" +#include "git2/clone.h" +#include "git2/commit.h" +#include "git2/common.h" +#include "git2/config.h" +#include "git2/diff.h" +#include "git2/errors.h" +#include "git2/filter.h" +#include "git2/graph.h" +#include "git2/ignore.h" +#include "git2/index.h" +#include "git2/indexer.h" +#include "git2/merge.h" +#include "git2/message.h" +#include "git2/net.h" +#include "git2/notes.h" +#include "git2/object.h" +#include "git2/odb.h" +#include "git2/oid.h" +#include "git2/pack.h" +#include "git2/patch.h" +#include "git2/pathspec.h" +#include "git2/push.h" +#include "git2/refdb.h" +#include "git2/reflog.h" +#include "git2/refs.h" +#include "git2/refspec.h" +#include "git2/remote.h" +#include "git2/repository.h" +#include "git2/reset.h" +#include "git2/revparse.h" +#include "git2/revwalk.h" +#include "git2/signature.h" +#include "git2/stash.h" +#include "git2/status.h" +#include "git2/submodule.h" +#include "git2/tag.h" +#include "git2/threads.h" +#include "git2/transport.h" +#include "git2/tree.h" +#include "git2/types.h" #include "git2/version.h" -#include "git2/common.h" -#include "git2/threads.h" -#include "git2/errors.h" - -#include "git2/types.h" - -#include "git2/oid.h" -#include "git2/signature.h" -#include "git2/odb.h" - -#include "git2/repository.h" -#include "git2/revwalk.h" -#include "git2/merge.h" -#include "git2/graph.h" -#include "git2/refs.h" -#include "git2/reflog.h" -#include "git2/revparse.h" - -#include "git2/object.h" -#include "git2/blob.h" -#include "git2/commit.h" -#include "git2/tag.h" -#include "git2/tree.h" -#include "git2/diff.h" - -#include "git2/index.h" -#include "git2/config.h" -#include "git2/transport.h" -#include "git2/remote.h" -#include "git2/clone.h" -#include "git2/checkout.h" -#include "git2/push.h" - -#include "git2/attr.h" -#include "git2/ignore.h" -#include "git2/branch.h" -#include "git2/refspec.h" -#include "git2/net.h" -#include "git2/status.h" -#include "git2/indexer.h" -#include "git2/submodule.h" -#include "git2/notes.h" -#include "git2/reset.h" -#include "git2/message.h" -#include "git2/pack.h" -#include "git2/stash.h" - #endif diff --git a/include/git2/blame.h b/include/git2/blame.h new file mode 100644 index 000000000..73bcc5bc6 --- /dev/null +++ b/include/git2/blame.h @@ -0,0 +1,198 @@ +/* + * 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_git_blame_h__ +#define INCLUDE_git_blame_h__ + +#include "common.h" +#include "oid.h" + +/** + * @file git2/blame.h + * @brief Git blame routines + * @defgroup git_blame Git blame routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Flags for indicating option behavior for git_blame APIs. + */ +typedef enum { + /** Normal blame, the default */ + GIT_BLAME_NORMAL = 0, + /** Track lines that have moved within a file (like `git blame -M`). + * NOT IMPLEMENTED. */ + GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0), + /** Track lines that have moved across files in the same commit (like `git blame -C`). + * NOT IMPLEMENTED. */ + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1), + /** Track lines that have been copied from another file that exists in the + * same commit (like `git blame -CC`). Implies SAME_FILE. + * NOT IMPLEMENTED. */ + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2), + /** Track lines that have been copied from another file that exists in *any* + * commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES. + * NOT IMPLEMENTED. */ + GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3), +} git_blame_flag_t; + +/** + * Blame options structure + * + * Use zeros to indicate default settings. It's easiest to use the + * `GIT_BLAME_OPTIONS_INIT` macro: + * git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + * + * - `flags` is a combination of the `git_blame_flag_t` values above. + * - `min_match_characters` is the lower bound on the number of alphanumeric + * characters that must be detected as moving/copying within a file for it to + * associate those lines with the parent commit. The default value is 20. + * This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*` + * flags are specified. + * - `newest_commit` is the id of the newest commit to consider. The default + * is HEAD. + * - `oldest_commit` is the id of the oldest commit to consider. The default + * is the first commit encountered with a NULL parent. + * - `min_line` is the first line in the file to blame. The default is 1 (line + * numbers start with 1). + * - `max_line` is the last line in the file to blame. The default is the last + * line of the file. + */ + +typedef struct git_blame_options { + unsigned int version; + + uint32_t flags; + uint16_t min_match_characters; + git_oid newest_commit; + git_oid oldest_commit; + uint32_t min_line; + uint32_t max_line; +} git_blame_options; + +#define GIT_BLAME_OPTIONS_VERSION 1 +#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION} + +/** + * Structure that represents a blame hunk. + * + * - `lines_in_hunk` is the number of lines in this hunk + * - `final_commit_id` is the OID of the commit where this line was last + * changed. + * - `final_start_line_number` is the 1-based line number where this hunk + * begins, in the final version of the file + * - `orig_commit_id` is the OID of the commit where this hunk was found. This + * will usually be the same as `final_commit_id`, except when + * `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified. + * - `orig_path` is the path to the file where this hunk originated, as of the + * commit specified by `orig_commit_id`. + * - `orig_start_line_number` is the 1-based line number where this hunk begins + * in the file named by `orig_path` in the commit specified by + * `orig_commit_id`. + * - `boundary` is 1 iff the hunk has been tracked to a boundary commit (the + * root, or the commit specified in git_blame_options.oldest_commit) + */ +typedef struct git_blame_hunk { + uint16_t lines_in_hunk; + + git_oid final_commit_id; + uint16_t final_start_line_number; + git_signature *final_signature; + + git_oid orig_commit_id; + const char *orig_path; + uint16_t orig_start_line_number; + git_signature *orig_signature; + + char boundary; +} git_blame_hunk; + + +/* Opaque structure to hold blame results */ +typedef struct git_blame git_blame; + +/** + * Gets the number of hunks that exist in the blame structure. + */ +GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame); + +/** + * Gets the blame hunk at the given index. + * + * @param blame the blame structure to query + * @param index index of the hunk to retrieve + * @return the hunk at the given index, or NULL on error + */ +GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex( + git_blame *blame, + uint32_t index); + +/** + * Gets the hunk that relates to the given line number in the newest commit. + * + * @param blame the blame structure to query + * @param lineno the (1-based) line number to find a hunk for + * @return the hunk that contains the given line, or NULL on error + */ +GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline( + git_blame *blame, + uint32_t lineno); + +/** + * Get the blame for a single file. + * + * @param out pointer that will receive the blame object + * @param repo repository whose history is to be walked + * @param path path to file to consider + * @param options options for the blame operation. If NULL, this is treated as + * though GIT_BLAME_OPTIONS_INIT were passed. + * @return 0 on success, or an error code. (use giterr_last for information + * about the error.) + */ +GIT_EXTERN(int) git_blame_file( + git_blame **out, + git_repository *repo, + const char *path, + git_blame_options *options); + + +/** + * Get blame data for a file that has been modified in memory. The `reference` + * parameter is a pre-calculated blame for the in-odb history of the file. This + * means that once a file blame is completed (which can be expensive), updating + * the buffer blame is very fast. + * + * Lines that differ between the buffer and the committed version are marked as + * having a zero OID for their final_commit_id. + * + * @param out pointer that will receive the resulting blame data + * @param reference cached blame from the history of the file (usually the output + * from git_blame_file) + * @param buffer the (possibly) modified contents of the file + * @param buffer_len number of valid bytes in the buffer + * @return 0 on success, or an error code. (use giterr_last for information + * about the error) + */ +GIT_EXTERN(int) git_blame_buffer( + git_blame **out, + git_blame *reference, + const char *buffer, + uint32_t buffer_len); + +/** + * Free memory allocated by git_blame_file or git_blame_buffer. + * + * @param blame the blame structure to free + */ +GIT_EXTERN(void) git_blame_free(git_blame *blame); + +/** @} */ +GIT_END_DECL +#endif + diff --git a/include/git2/blob.h b/include/git2/blob.h index 8fca48966..dcab4fbe0 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "object.h" +#include "buffer.h" /** * @file git2/blob.h @@ -95,6 +96,37 @@ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob); */ GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob); +/** + * Get a buffer with the filtered content of a blob. + * + * This applies filters as if the blob was being checked out to the + * working directory under the specified filename. This may apply + * CRLF filtering or other types of changes depending on the file + * attributes set for the blob and the content detected in it. + * + * The output is written into a `git_buf` which the caller must free + * when done (via `git_buf_free`). + * + * If no filters need to be applied, then the `out` buffer will just be + * populated with a pointer to the raw content of the blob. In that case, + * be careful to *not* free the blob until done with the buffer. To keep + * the data detached from the blob, call `git_buf_grow` on the buffer + * with a `want_size` of 0 and the buffer will be reallocated to be + * detached from the blob. + * + * @param out The git_buf to be filled in + * @param blob Pointer to the blob + * @param as_path Path used for file attribute lookups, etc. + * @param check_for_binary_data Should this test if blob content contains + * NUL bytes / looks like binary data before applying filters? + * @return 0 on success or an error code + */ +GIT_EXTERN(int) git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *as_path, + int check_for_binary_data); + /** * Read a file from the working folder of a repository * and write it to the Object Database as a loose blob diff --git a/include/git2/branch.h b/include/git2/branch.h index de414e9b0..44d6fd9c3 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -66,33 +66,41 @@ GIT_EXTERN(int) git_branch_create( */ GIT_EXTERN(int) git_branch_delete(git_reference *branch); -typedef int (*git_branch_foreach_cb)( - const char *branch_name, - git_branch_t branch_type, - void *payload); +/** Iterator type for branches */ +typedef struct git_branch_iterator git_branch_iterator; /** - * Loop over all the branches and issue a callback for each one. - * - * If the callback returns a non-zero value, this will stop looping. + * Create an iterator which loops over the requested branches. * + * @param out the iterator * @param repo Repository where to find the branches. - * * @param list_flags Filtering flags for the branch * listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE * or a combination of the two. * - * @param branch_cb Callback to invoke per found branch. - * - * @param payload Extra parameter to callback function. - * - * @return 0 on success, GIT_EUSER on non-zero callback, or error code + * @return 0 on success or an error code */ -GIT_EXTERN(int) git_branch_foreach( +GIT_EXTERN(int) git_branch_iterator_new( + git_branch_iterator **out, git_repository *repo, - unsigned int list_flags, - git_branch_foreach_cb branch_cb, - void *payload); + git_branch_t list_flags); + +/** + * Retrieve the next branch from the iterator + * + * @param out the reference + * @param out_type the type of branch (local or remote-tracking) + * @param iter the branch iterator + * @return 0 on success, GIT_ITEROVER if there are no more branches or an error code. + */ +GIT_EXTERN(int) git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *iter); + +/** + * Free a branch iterator + * + * @param iter the iterator to free + */ +GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter); /** * Move/rename an existing local branch reference. diff --git a/include/git2/buffer.h b/include/git2/buffer.h new file mode 100644 index 000000000..36a61e6c9 --- /dev/null +++ b/include/git2/buffer.h @@ -0,0 +1,112 @@ +/* + * 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_git_buf_h__ +#define INCLUDE_git_buf_h__ + +#include "common.h" + +/** + * @file git2/buffer.h + * @brief Buffer export structure + * + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * A data buffer for exporting data from libgit2 + * + * Sometimes libgit2 wants to return an allocated data buffer to the + * caller and have the caller take responsibility for freeing that memory. + * This can be awkward if the caller does not have easy access to the same + * allocation functions that libgit2 is using. In those cases, libgit2 + * will fill in a `git_buf` and the caller can use `git_buf_free()` to + * release it when they are done. + * + * A `git_buf` may also be used for the caller to pass in a reference to + * a block of memory they hold. In this case, libgit2 will not resize or + * free the memory, but will read from it as needed. + * + * A `git_buf` is a public structure with three fields: + * + * - `ptr` points to the start of the allocated memory. If it is NULL, + * then the `git_buf` is considered empty and libgit2 will feel free + * to overwrite it with new data. + * + * - `size` holds the size (in bytes) of the data that is actually used. + * + * - `asize` holds the known total amount of allocated memory if the `ptr` + * was allocated by libgit2. It may be larger than `size`. If `ptr` + * was not allocated by libgit2 and should not be resized and/or freed, + * then `asize` will be set to zero. + * + * Some APIs may occasionally do something slightly unusual with a buffer, + * such as setting `ptr` to a value that was passed in by the user. In + * those cases, the behavior will be clearly documented by the API. + */ +typedef struct { + char *ptr; + size_t asize, size; +} git_buf; + +/** + * Static initializer for git_buf from static buffer + */ +#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) } + +/** + * Free the memory referred to by the git_buf. + * + * Note that this does not free the `git_buf` itself, just the memory + * pointed to by `buffer->ptr`. This will not free the memory if it looks + * like it was not allocated internally, but it will clear the buffer back + * to the empty state. + * + * @param buffer The buffer to deallocate + */ +GIT_EXTERN(void) git_buf_free(git_buf *buffer); + +/** + * Resize the buffer allocation to make more space. + * + * This will attempt to grow the buffer to accomodate the target size. + * + * If the buffer refers to memory that was not allocated by libgit2 (i.e. + * the `asize` field is zero), then `ptr` will be replaced with a newly + * allocated block of data. Be careful so that memory allocated by the + * caller is not lost. As a special variant, if you pass `target_size` as + * 0 and the memory is not allocated by libgit2, this will allocate a new + * buffer of size `size` and copy the external data into it. + * + * Currently, this will never shrink a buffer, only expand it. + * + * If the allocation fails, this will return an error and the buffer will be + * marked as invalid for future operations, invaliding the contents. + * + * @param buffer The buffer to be resized; may or may not be allocated yet + * @param target_size The desired available size + * @return 0 on success, -1 on allocation failure + */ +GIT_EXTERN(int) git_buf_grow(git_buf *buffer, size_t target_size); + +/** + * Set buffer to a copy of some raw data. + * + * @param buffer The buffer to set + * @param data The data to copy into the buffer + * @param datalen The length of the data to copy into the buffer + * @return 0 on success, -1 on allocation failure + */ +GIT_EXTERN(int) git_buf_set( + git_buf *buffer, const void *data, size_t datalen); + +GIT_END_DECL + +/** @} */ + +#endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h index a086408c7..efafdc3e4 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -131,6 +131,13 @@ typedef enum { /** Don't refresh index/config/etc before doing checkout */ GIT_CHECKOUT_NO_REFRESH = (1u << 9), + /** Allow checkout to skip unmerged files */ + GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10), + /** For unmerged files, checkout stage 2 from index */ + GIT_CHECKOUT_USE_OURS = (1u << 11), + /** For unmerged files, checkout stage 3 from index */ + GIT_CHECKOUT_USE_THEIRS = (1u << 12), + /** Treat pathspec as simple list of exact match file paths */ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13), @@ -141,13 +148,6 @@ typedef enum { * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ - /** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */ - GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10), - /** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */ - GIT_CHECKOUT_USE_OURS = (1u << 11), - /** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */ - GIT_CHECKOUT_USE_THEIRS = (1u << 12), - /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16), /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */ @@ -238,6 +238,9 @@ typedef struct git_checkout_opts { git_tree *baseline; /** expected content of workdir, defaults to HEAD */ const char *target_directory; /** alternative checkout path to workdir */ + + const char *our_label; /** the name of the "our" side of conflicts */ + const char *their_label; /** the name of the "their" side of conflicts */ } git_checkout_opts; #define GIT_CHECKOUT_OPTS_VERSION 1 @@ -249,13 +252,13 @@ typedef struct git_checkout_opts { * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) - * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing + * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing * branch, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_head( git_repository *repo, - git_checkout_opts *opts); + const git_checkout_opts *opts); /** * Updates files in the working tree to match the content of the index. @@ -269,7 +272,7 @@ GIT_EXTERN(int) git_checkout_head( GIT_EXTERN(int) git_checkout_index( git_repository *repo, git_index *index, - git_checkout_opts *opts); + const git_checkout_opts *opts); /** * Updates files in the index and working tree to match the content of the @@ -277,7 +280,7 @@ GIT_EXTERN(int) git_checkout_index( * * @param repo repository to check out (must be non-bare) * @param treeish a commit, tag or tree which content will be used to update - * the working directory + * the working directory (or NULL to use HEAD) * @param opts specifies checkout options (may be NULL) * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) @@ -285,7 +288,7 @@ GIT_EXTERN(int) git_checkout_index( GIT_EXTERN(int) git_checkout_tree( git_repository *repo, const git_object *treeish, - git_checkout_opts *opts); + const git_checkout_opts *opts); /** @} */ GIT_END_DECL diff --git a/include/git2/clone.h b/include/git2/clone.h index 5858b4e32..331cf92e7 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -35,30 +35,12 @@ GIT_BEGIN_DECL * set the `checkout_strategy` to GIT_CHECKOUT_DEFAULT. * - `bare` should be set to zero to create a standard repo, non-zero for * a bare repo - * - `fetch_progress_cb` is optional callback for fetch progress. Be aware that - * this is called inline with network and indexing operations, so performance - * may be affected. - * - `fetch_progress_payload` is payload for fetch_progress_cb + * - `ignore_cert_errors` should be set to 1 if errors validating the remote host's + * certificate should be ignored. * * ** "origin" remote options: ** * - `remote_name` is the name given to the "origin" remote. The default is * "origin". - * - `pushurl` is a URL to be used for pushing. NULL means use the fetch url. - * - `fetch_spec` is the fetch specification to be used for fetching. NULL - * results in the same behavior as GIT_REMOTE_DEFAULT_FETCH. - * - `push_spec` is the fetch specification to be used for pushing. NULL means - * use the same spec as for fetching. - * - `cred_acquire_cb` is a callback to be used if credentials are required - * during the initial fetch. - * - `cred_acquire_payload` is the payload for the above callback. - * - `transport_flags` is flags used to create transport if no transport is - * provided. - * - `transport` is a custom transport to be used for the initial fetch. NULL - * means use the transport autodetected from the URL. - * - `remote_callbacks` may be used to specify custom progress callbacks for - * the origin remote before the fetch is initiated. - * - `remote_autotag` may be used to specify the autotag setting before the - * initial fetch. The default is GIT_REMOTE_DOWNLOAD_TAGS_ALL. * - `checkout_branch` gives the name of the branch to checkout. NULL means * use the remote's HEAD. */ @@ -67,29 +49,23 @@ typedef struct git_clone_options { unsigned int version; git_checkout_opts checkout_opts; - int bare; - git_transfer_progress_callback fetch_progress_cb; - void *fetch_progress_payload; + git_remote_callbacks remote_callbacks; + int bare; + int ignore_cert_errors; const char *remote_name; - const char *pushurl; - const char *fetch_spec; - const char *push_spec; - git_cred_acquire_cb cred_acquire_cb; - void *cred_acquire_payload; - git_transport_flags_t transport_flags; - git_transport *transport; - git_remote_callbacks *remote_callbacks; - git_remote_autotag_option_t remote_autotag; const char* checkout_branch; } git_clone_options; #define GIT_CLONE_OPTIONS_VERSION 1 -#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}} +#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}, GIT_REMOTE_CALLBACKS_INIT} /** - * Clone a remote repository, and checkout the branch pointed to by the remote - * HEAD. + * Clone a remote repository. + * + * This version handles the simple case. If you'd like to create the + * repository or remote with non-default settings, you can create and + * configure them and then use `git_clone_into()`. * * @param out pointer that will receive the resulting repository object * @param url the remote repository to clone @@ -105,6 +81,22 @@ GIT_EXTERN(int) git_clone( const char *local_path, const git_clone_options *options); +/** + * Clone into a repository + * + * After creating the repository and remote and configuring them for + * paths and callbacks respectively, you can call this function to + * perform the clone operation and optionally checkout files. + * + * @param repo the repository to use + * @param remote the remote repository to clone from + * @param co_opts options to use during checkout + * @param branch the branch to checkout after the clone, pass NULL for the remote's + * default branch + * @return 0 on success or an error code + */ +GIT_EXTERN(int) git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 544d21d87..a08cf1c6c 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -24,17 +24,24 @@ GIT_BEGIN_DECL /** * Lookup a commit object from a repository. * + * The returned object should be released with `git_commit_free` when no + * longer needed. + * * @param commit pointer to the looked up commit * @param repo the repo to use when locating the commit. * @param id identity of the commit to locate. If the object is * an annotated tag it will be peeled back to the commit. * @return 0 or an error code */ -GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id); +GIT_EXTERN(int) git_commit_lookup( + git_commit **commit, git_repository *repo, const git_oid *id); /** - * Lookup a commit object from a repository, - * given a prefix of its identifier (short id). + * Lookup a commit object from a repository, given a prefix of its + * identifier (short id). + * + * The returned object should be released with `git_commit_free` when no + * longer needed. * * @see git_object_lookup_prefix * @@ -45,7 +52,8 @@ GIT_EXTERN(int) git_commit_lookup(git_commit **commit, git_repository *repo, con * @param len the length of the short identifier * @return 0 or an error code */ -GIT_EXTERN(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, size_t len); +GIT_EXTERN(int) git_commit_lookup_prefix( + git_commit **commit, git_repository *repo, const git_oid *id, size_t len); /** * Close an open commit @@ -92,11 +100,22 @@ GIT_EXTERN(const char *) git_commit_message_encoding(const git_commit *commit); /** * Get the full message of a commit. * + * The returned message will be slightly prettified by removing any + * potential leading newlines. + * * @param commit a previously loaded commit. * @return the message of a commit */ GIT_EXTERN(const char *) git_commit_message(const git_commit *commit); +/** + * Get the full raw message of a commit. + * + * @param commit a previously loaded commit. + * @return the raw message of a commit + */ +GIT_EXTERN(const char *) git_commit_message_raw(const git_commit *commit); + /** * Get the commit time (i.e. committer time) of a commit. * @@ -129,6 +148,14 @@ GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit) */ GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit); +/** + * Get the full raw text of the commit header. + * + * @param commit a previously loaded commit + * @return the header text of the commit + */ +GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit); + /** * Get the tree pointed to by a commit. * diff --git a/include/git2/common.h b/include/git2/common.h index b52e13918..dca0c9c21 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -105,7 +105,8 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); */ typedef enum { GIT_CAP_THREADS = ( 1 << 0 ), - GIT_CAP_HTTPS = ( 1 << 1 ) + GIT_CAP_HTTPS = ( 1 << 1 ), + GIT_CAP_SSH = ( 1 << 2 ), } git_cap_t; /** @@ -135,7 +136,9 @@ typedef enum { GIT_OPT_SET_CACHE_OBJECT_LIMIT, GIT_OPT_SET_CACHE_MAX_SIZE, GIT_OPT_ENABLE_CACHING, - GIT_OPT_GET_CACHED_MEMORY + GIT_OPT_GET_CACHED_MEMORY, + GIT_OPT_GET_TEMPLATE_PATH, + GIT_OPT_SET_TEMPLATE_PATH } git_libgit2_opt_t; /** @@ -209,6 +212,18 @@ typedef enum { * > Get the current bytes in cache and the maximum that would be * > allowed in the cache. * + * * opts(GIT_OPT_GET_TEMPLATE_PATH, char *out, size_t len) + * + * > Get the default template path. + * > The path is written to the `out` + * > buffer up to size `len`. Returns GIT_EBUFS if buffer is too small. + * + * * opts(GIT_OPT_SET_TEMPLATE_PATH, const char *path) + * + * > Set the default template path. + * > + * > - `path` directory of template. + * * @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 827d43544..95da4bc03 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -61,6 +61,7 @@ typedef struct { } git_config_entry; typedef int (*git_config_foreach_cb)(const git_config_entry *, void *); +typedef struct git_config_iterator git_config_iterator; typedef enum { GIT_CVAR_FALSE = 0, @@ -327,7 +328,7 @@ GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name); /** - * Get each value of a multivar. + * Get each value of a multivar in a foreach callback * * The callback will be called on each variable found * @@ -338,7 +339,34 @@ GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, c * @param callback the function to be called on each value of the variable * @param payload opaque pointer to pass to the callback */ -GIT_EXTERN(int) git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); +GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); + +/** + * Get each value of a multivar + * + * @param out pointer to store the iterator + * @param cfg where to look for the variable + * @param name the variable's name + * @param regexp regular expression to filter which variables we're + * interested in. Use NULL to indicate all + */ +GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); + +/** + * Return the current entry and advance the iterator + * + * @param entry pointer to store the entry + * @param iter the iterator + * @return 0 or an error code. GIT_ITEROVER if the iteration has completed + */ +GIT_EXTERN(int) git_config_next(git_config_entry **entry, git_config_iterator *iter); + +/** + * Free a config iterator + * + * @param iter the iterator to free + */ +GIT_EXTERN(void) git_config_iterator_free(git_config_iterator *iter); /** * Set the value of an integer config variable in the config file @@ -406,6 +434,17 @@ GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const */ GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name); +/** + * Deletes one or several entries from a multivar in the local config file. + * + * @param cfg where to look for the variables + * @param name the variable's name + * @param regexp a regular expression to indicate which values to delete + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp); + /** * Perform an operation on each config variable. * @@ -424,6 +463,29 @@ GIT_EXTERN(int) git_config_foreach( git_config_foreach_cb callback, void *payload); +/** + * Iterate over all the config variables + * + * Use `git_config_next` to advance the iteration and + * `git_config_iterator_free` when done. + * + * @param out pointer to store the iterator + * @param cfg where to ge the variables from + */ +GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg); + +/** + * Iterate over all the config variables whose name matches a pattern + * + * Use `git_config_next` to advance the iteration and + * `git_config_iterator_free` when done. + * + * @param out pointer to store the iterator + * @param cfg where to ge the variables from + * @param regexp regular expression to match the names + */ +GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp); + /** * Perform an operation on each config variable matching a regular expression. * @@ -535,6 +597,25 @@ GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value); GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value); +/** + * Perform an operation on each config variable in given config backend + * matching a regular expression. + * + * This behaviors like `git_config_foreach_match` except instead of all config + * entries it just enumerates through the given backend entry. + * + * @param backend where to get the variables from + * @param regexp regular expression to match against config names (can be NULL) + * @param callback the function to call on each variable + * @param payload the data to pass to the callback + */ +GIT_EXTERN(int) git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + int (*fn)(const git_config_entry *, void *), + void *data); + + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h index 5d93cf4dd..1d8809211 100644 --- a/include/git2/cred_helpers.h +++ b/include/git2/cred_helpers.h @@ -7,7 +7,7 @@ #ifndef INCLUDE_git_cred_helpers_h__ #define INCLUDE_git_cred_helpers_h__ -#include "git2/transport.h" +#include "transport.h" /** * @file git2/cred_helpers.h diff --git a/include/git2/diff.h b/include/git2/diff.h index 43029c49c..61d288380 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -20,34 +20,38 @@ * Overview * -------- * - * Calculating diffs is generally done in two phases: building a diff list - * then traversing the diff list. This makes is easier to share logic - * across the various types of diffs (tree vs tree, workdir vs index, etc.), - * and also allows you to insert optional diff list post-processing phases, - * such as rename detected, in between the steps. When you are done with a - * diff list object, it must be freed. + * Calculating diffs is generally done in two phases: building a list of + * diffs then traversing it. This makes is easier to share logic across + * the various types of diffs (tree vs tree, workdir vs index, etc.), and + * also allows you to insert optional diff post-processing phases, + * such as rename detection, in between the steps. When you are done with + * a diff object, it must be freed. * * Terminology * ----------- * * To understand the diff APIs, you should know the following terms: * - * - A `diff` or `diff list` represents the cumulative list of differences - * between two snapshots of a repository (possibly filtered by a set of - * file name patterns). This is the `git_diff_list` object. + * - A `diff` represents the cumulative list of differences between two + * snapshots of a repository (possibly filtered by a set of file name + * patterns). This is the `git_diff` object. + * * - A `delta` is a file pair with an old and new revision. The old version * may be absent if the file was just created and the new version may be * absent if the file was deleted. A diff is mostly just a list of deltas. + * * - A `binary` file / delta is a file (or pair) for which no text diffs - * should be generated. A diff list can contain delta entries that are + * should be generated. A diff can contain delta entries that are * binary, but no diff content will be output for those files. There is * a base heuristic for binary detection and you can further tune the * behavior with git attributes or diff flags and option settings. + * * - A `hunk` is a span of modified lines in a delta along with some stable * surrounding context. You can configure the amount of context and other * properties of how hunks are generated. Each hunk also comes with a * header that described where it starts and ends in both the old and new * versions in the delta. + * * - A `line` is a range of characters inside a hunk. It could be a context * line (i.e. in both old and new versions), an added line (i.e. only in * the new version), or a removed line (i.e. only in the old version). @@ -68,100 +72,125 @@ GIT_BEGIN_DECL typedef enum { /** Normal diff, the default */ GIT_DIFF_NORMAL = 0, + + /* + * Options controlling which files will be in the diff + */ + /** Reverse the sides of the diff */ - GIT_DIFF_REVERSE = (1 << 0), - /** Treat all files as text, disabling binary attributes & detection */ - GIT_DIFF_FORCE_TEXT = (1 << 1), - /** Ignore all whitespace */ - GIT_DIFF_IGNORE_WHITESPACE = (1 << 2), - /** Ignore changes in amount of whitespace */ - GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), - /** Ignore whitespace at end of line */ - GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), - /** Exclude submodules from the diff completely */ - GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), - /** Use the "patience diff" algorithm (currently unimplemented) */ - GIT_DIFF_PATIENCE = (1 << 6), - /** Include ignored files in the diff list */ - GIT_DIFF_INCLUDE_IGNORED = (1 << 7), - /** Include untracked files in the diff list */ - GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), - /** Include unmodified files in the diff list */ - GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), + GIT_DIFF_REVERSE = (1u << 0), + + /** Include ignored files in the diff */ + GIT_DIFF_INCLUDE_IGNORED = (1u << 1), + + /** Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory + * will be marked with only a single entry in the diff; this flag + * adds all files under the directory as IGNORED entries, too. + */ + GIT_DIFF_RECURSE_IGNORED_DIRS = (1u << 2), + + /** Include untracked files in the diff */ + GIT_DIFF_INCLUDE_UNTRACKED = (1u << 3), /** Even with GIT_DIFF_INCLUDE_UNTRACKED, an entire untracked - * directory will be marked with only a single entry in the diff list + * directory will be marked with only a single entry in the diff * (a la what core Git does in `git status`); this flag adds *all* * files under untracked directories as UNTRACKED entries, too. */ - GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), + GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1u << 4), - /** 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. - */ - GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11), - - /** Use case insensitive filename comparisons */ - GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12), - - /** When generating patch text, include the content of untracked - * files. This automatically turns on GIT_DIFF_INCLUDE_UNTRACKED but - * it does not turn on GIT_DIFF_RECURSE_UNTRACKED_DIRS. Add that - * flag if you want the content of every single UNTRACKED file. - */ - GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13), - - /** Disable updating of the `binary` flag in delta records. This is - * useful when iterating over a diff if you don't need hunk and data - * callbacks and want to avoid having to load file completely. - */ - GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14), + /** Include unmodified files in the diff */ + GIT_DIFF_INCLUDE_UNMODIFIED = (1u << 5), /** Normally, a type change between files will be converted into a * DELETED record for the old and an ADDED record for the new; this * options enabled the generation of TYPECHANGE delta records. */ - GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15), + GIT_DIFF_INCLUDE_TYPECHANGE = (1u << 6), /** Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still * generally show as a DELETED blob. This flag tries to correctly * label blob->tree transitions as TYPECHANGE records with new_file's * mode set to tree. Note: the tree SHA will not be available. */ - GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16), + GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1u << 7), /** Ignore file mode changes */ - GIT_DIFF_IGNORE_FILEMODE = (1 << 17), + GIT_DIFF_IGNORE_FILEMODE = (1u << 8), - /** Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory - * will be marked with only a single entry in the diff list; this flag - * adds all files under the directory as IGNORED entries, too. + /** Treat all submodules as unmodified */ + GIT_DIFF_IGNORE_SUBMODULES = (1u << 9), + + /** Use case insensitive filename comparisons */ + GIT_DIFF_IGNORE_CASE = (1u << 10), + + /** 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. */ - GIT_DIFF_RECURSE_IGNORED_DIRS = (1 << 18), + GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12), - /** Core Git scans inside untracked directories, labeling them IGNORED - * if they are empty or only contain ignored files; a directory is - * consider UNTRACKED only if it has an actual untracked file in it. - * This scan is extra work for a case you often don't care about. This - * flag makes libgit2 immediately label an untracked directory as - * UNTRACKED without looking inside it (which differs from core Git). - * Of course, ignore rules are still checked for the directory itself. + /** Disable updating of the `binary` flag in delta records. This is + * useful when iterating over a diff if you don't need hunk and data + * callbacks and want to avoid having to load file completely. */ - GIT_DIFF_FAST_UNTRACKED_DIRS = (1 << 19), + GIT_DIFF_SKIP_BINARY_CHECK = (1u << 13), + /** When diff finds an untracked directory, to match the behavior of + * core Git, it scans the contents for IGNORED and UNTRACKED files. + * If *all* contents are IGNORED, then the directory is IGNORED; if + * any contents are not IGNORED, then the directory is UNTRACKED. + * This is extra work that may not matter in many cases. This flag + * turns off that scan and immediately labels an untracked directory + * as UNTRACKED (changing the behavior to not match core Git). + */ + GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = (1u << 14), + + /* + * Options controlling how output will be generated + */ + + /** Treat all files as text, disabling binary attributes & detection */ + GIT_DIFF_FORCE_TEXT = (1u << 20), /** Treat all files as binary, disabling text diffs */ - GIT_DIFF_FORCE_BINARY = (1 << 20), + GIT_DIFF_FORCE_BINARY = (1u << 21), + + /** Ignore all whitespace */ + GIT_DIFF_IGNORE_WHITESPACE = (1u << 22), + /** Ignore changes in amount of whitespace */ + GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1u << 23), + /** Ignore whitespace at end of line */ + GIT_DIFF_IGNORE_WHITESPACE_EOL = (1u << 24), + + /** When generating patch text, include the content of untracked + * files. This automatically turns on GIT_DIFF_INCLUDE_UNTRACKED but + * it does not turn on GIT_DIFF_RECURSE_UNTRACKED_DIRS. Add that + * flag if you want the content of every single UNTRACKED file. + */ + GIT_DIFF_SHOW_UNTRACKED_CONTENT = (1u << 25), + + /** When generating output, include the names of unmodified files if + * they are included in the git_diff. Normally these are skipped in + * the formats that list files (e.g. name-only, name-status, raw). + * Even with this, these will not be included in patch format. + */ + GIT_DIFF_SHOW_UNMODIFIED = (1u << 26), + + /** Use the "patience diff" algorithm */ + GIT_DIFF_PATIENCE = (1u << 28), + /** Take extra time to find minimal diff */ + GIT_DIFF_MINIMAL = (1 << 29), + } git_diff_option_t; /** - * The diff list object that contains all individual file deltas. + * The diff object that contains all individual file deltas. * * This is an opaque structure which will be allocated by one of the diff * generator functions below (such as `git_diff_tree_to_tree`). You are * responsible for releasing the object memory when done, using the - * `git_diff_list_free()` function. + * `git_diff_free()` function. */ -typedef struct git_diff_list git_diff_list; +typedef struct git_diff git_diff; /** * Flags for the delta object and the file objects on each side. @@ -172,16 +201,16 @@ typedef struct git_diff_list git_diff_list; * considered reserved for internal or future use. */ typedef enum { - GIT_DIFF_FLAG_BINARY = (1 << 0), /** file(s) treated as binary data */ - GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), /** file(s) treated as text data */ - GIT_DIFF_FLAG_VALID_OID = (1 << 2), /** `oid` value is known correct */ + GIT_DIFF_FLAG_BINARY = (1u << 0), /** file(s) treated as binary data */ + GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /** file(s) treated as text data */ + GIT_DIFF_FLAG_VALID_OID = (1u << 2), /** `oid` value is known correct */ } git_diff_flag_t; /** * What type of change is described by a git_diff_delta? * * `GIT_DELTA_RENAMED` and `GIT_DELTA_COPIED` will only show up if you run - * `git_diff_find_similar()` on the diff list object. + * `git_diff_find_similar()` on the diff object. * * `GIT_DELTA_TYPECHANGE` only shows up given `GIT_DIFF_INCLUDE_TYPECHANGE` * in the option flags (otherwise type changes will be split into ADDED / @@ -200,11 +229,11 @@ typedef enum { } git_delta_t; /** - * Description of one side of a diff entry. + * Description of one side of a delta. * - * Although this is called a "file", it may actually represent a file, a - * symbolic link, a submodule commit id, or even a tree (although that only - * if you are tracking type changes or ignored/untracked directories). + * Although this is called a "file", it could represent a file, a symbolic + * link, a submodule commit id, or even a tree (although that only if you + * are tracking type changes or ignored/untracked directories). * * The `oid` is the `git_oid` of the item. If the entry represents an * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta), @@ -231,9 +260,8 @@ typedef struct { /** * Description of changes to one entry. * - * When iterating over a diff list object, this will be passed to most - * callback functions and you can use the contents to understand exactly - * what has changed. + * When iterating over a diff, this will be passed to most callbacks and + * you can use the contents to understand exactly what has changed. * * The `old_file` represents the "from" side of the diff and the `new_file` * represents to "to" side of the diff. What those means depend on the @@ -266,28 +294,29 @@ typedef struct { * the score (a la `printf("M%03d", 100 - delta->similarity)`). */ typedef struct { + git_delta_t status; + uint32_t flags; /**< git_diff_flag_t values */ + uint16_t similarity; /**< for RENAMED and COPIED, value 0-100 */ + uint16_t nfiles; /**< number of files in this delta */ git_diff_file old_file; git_diff_file new_file; - git_delta_t status; - uint32_t similarity; /**< for RENAMED and COPIED, value 0-100 */ - uint32_t flags; } git_diff_delta; /** * Diff notification callback function. * * The callback will be called for each file, just before the `git_delta_t` - * gets inserted into the diff list. + * gets inserted into the diff. * * When the callback: * - returns < 0, the diff process will be aborted. - * - returns > 0, the delta will not be inserted into the diff list, but the + * - returns > 0, the delta will not be inserted into the diff, but the * diff process continues. - * - returns 0, the delta is inserted into the diff list, and the diff process + * - returns 0, the delta is inserted into the diff, and the diff process * continues. */ typedef int (*git_diff_notify_cb)( - const git_diff_list *diff_so_far, + const git_diff *diff_so_far, const git_diff_delta *delta_to_add, const char *matched_pathspec, void *payload); @@ -314,28 +343,44 @@ typedef int (*git_diff_notify_cb)( * - `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 + * - `ignore_submodules` overrides the submodule ignore setting for all + * submodules in the diff. */ typedef struct { unsigned int version; /**< version for the struct */ uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ - uint16_t context_lines; /**< defaults to 3 */ - uint16_t interhunk_lines; /**< defaults to 0 */ - const char *old_prefix; /**< defaults to "a" */ - const char *new_prefix; /**< defaults to "b" */ - git_strarray pathspec; /**< defaults to include all paths */ - git_off_t max_size; /**< defaults to 512MB */ + + /* options controlling which files are in the diff */ + + 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; + void *notify_payload; + + /* options controlling how to diff text is generated */ + + uint16_t context_lines; /**< defaults to 3 */ + uint16_t interhunk_lines; /**< defaults to 0 */ + uint16_t oid_abbrev; /**< default 'core.abbrev' or 7 if unset */ + git_off_t max_size; /**< defaults to 512MB */ + const char *old_prefix; /**< defaults to "a" */ + const char *new_prefix; /**< defaults to "b" */ } git_diff_options; +/* The current version of the diff options structure */ #define GIT_DIFF_OPTIONS_VERSION 1 -#define GIT_DIFF_OPTIONS_INIT {GIT_DIFF_OPTIONS_VERSION, GIT_DIFF_NORMAL, 3} + +/* Stack initializer for diff options. Alternatively use + * `git_diff_options_init` programmatic initialization. + */ +#define GIT_DIFF_OPTIONS_INIT \ + {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_DEFAULT, {NULL,0}, NULL, NULL, 3} /** * When iterating over a diff, callback that will be made per file. * * @param delta A pointer to the delta data for the file - * @param progress Goes from 0 to 1 over the diff list + * @param progress Goes from 0 to 1 over the diff * @param payload User-specified pointer from foreach function */ typedef int (*git_diff_file_cb)( @@ -346,34 +391,35 @@ typedef int (*git_diff_file_cb)( /** * Structure describing a hunk of a diff. */ -typedef struct { - int old_start; /** Starting line number in old_file */ - int old_lines; /** Number of lines in old_file */ - int new_start; /** Starting line number in new_file */ - int new_lines; /** Number of lines in new_file */ -} git_diff_range; +typedef struct git_diff_hunk git_diff_hunk; +struct git_diff_hunk { + int old_start; /** Starting line number in old_file */ + int old_lines; /** Number of lines in old_file */ + int new_start; /** Starting line number in new_file */ + int new_lines; /** Number of lines in new_file */ + size_t header_len; /** Number of bytes in header text */ + char header[128]; /** Header text, NUL-byte terminated */ +}; /** * When iterating over a diff, callback that will be made per hunk. */ typedef int (*git_diff_hunk_cb)( const git_diff_delta *delta, - const git_diff_range *range, - const char *header, - size_t header_len, + const git_diff_hunk *hunk, void *payload); /** * Line origin constants. * * These values describe where a line came from and will be passed to - * the git_diff_data_cb when iterating over a diff. There are some + * the git_diff_line_cb when iterating over a diff. There are some * special origin constants at the end that are used for the text * output callbacks to demarcate lines that are actually part of * the file or hunk headers. */ typedef enum { - /* These values will be sent to `git_diff_data_cb` along with the line */ + /* These values will be sent to `git_diff_line_cb` along with the line */ GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_DELETION = '-', @@ -382,15 +428,28 @@ typedef enum { GIT_DIFF_LINE_ADD_EOFNL = '>', /**< Old has no LF at end, new does */ GIT_DIFF_LINE_DEL_EOFNL = '<', /**< Old has LF at end, new does not */ - /* The following values will only be sent to a `git_diff_data_cb` when - * the content of a diff is being formatted (eg. through - * git_diff_print_patch() or git_diff_print_compact(), for instance). + /* The following values will only be sent to a `git_diff_line_cb` when + * the content of a diff is being formatted through `git_diff_print`. */ GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_HUNK_HDR = 'H', - GIT_DIFF_LINE_BINARY = 'B' + GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */ } git_diff_line_t; +/** + * Structure describing a line (or data span) of a diff. + */ +typedef struct git_diff_line git_diff_line; +struct git_diff_line { + char origin; /** A git_diff_line_t value */ + int old_lineno; /** Line number in old file or -1 for added line */ + int new_lineno; /** Line number in new file or -1 for deleted line */ + int num_lines; /** Number of newline characters in content */ + size_t content_len; /** Number of bytes of data */ + git_off_t content_offset; /** Offset in the original file to the content */ + const char *content; /** Pointer to diff text, not NUL-byte terminated */ +}; + /** * When iterating over a diff, callback that will be made per text diff * line. In this context, the provided range will be NULL. @@ -399,46 +458,36 @@ typedef enum { * of text. This uses some extra GIT_DIFF_LINE_... constants for output * of lines of file and hunk headers. */ -typedef int (*git_diff_data_cb)( +typedef int (*git_diff_line_cb)( const git_diff_delta *delta, /** delta that contains this data */ - const git_diff_range *range, /** range of lines containing this data */ - char line_origin, /** git_diff_list_t value from above */ - const char *content, /** diff data - not NUL terminated */ - size_t content_len, /** number of bytes of diff data */ + const git_diff_hunk *hunk, /** hunk containing this data */ + const git_diff_line *line, /** line data */ void *payload); /** user reference data */ -/** - * The diff patch is used to store all the text diffs for a delta. - * - * You can easily loop over the content of patches and get information about - * them. - */ -typedef struct git_diff_patch git_diff_patch; - /** * Flags to control the behavior of diff rename/copy detection. */ typedef enum { /** look for renames? (`--find-renames`) */ - GIT_DIFF_FIND_RENAMES = (1 << 0), + GIT_DIFF_FIND_RENAMES = (1u << 0), /** consider old side of modified for renames? (`--break-rewrites=N`) */ - GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1 << 1), + GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1u << 1), /** look for copies? (a la `--find-copies`) */ - GIT_DIFF_FIND_COPIES = (1 << 2), + GIT_DIFF_FIND_COPIES = (1u << 2), /** consider unmodified as copy sources? (`--find-copies-harder`) */ - GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3), + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1u << 3), /** mark large rewrites for split (`--break-rewrites=/M`) */ - GIT_DIFF_FIND_REWRITES = (1 << 4), + GIT_DIFF_FIND_REWRITES = (1u << 4), /** actually split large rewrites into delete/add pairs */ - GIT_DIFF_BREAK_REWRITES = (1 << 5), + GIT_DIFF_BREAK_REWRITES = (1u << 5), /** mark rewrites for split and break into delete/add pairs */ GIT_DIFF_FIND_AND_BREAK_REWRITES = (GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES), /** find renames/copies for untracked items in working directory */ - GIT_DIFF_FIND_FOR_UNTRACKED = (1 << 6), + GIT_DIFF_FIND_FOR_UNTRACKED = (1u << 6), /** turn on all finding features */ GIT_DIFF_FIND_ALL = (0x0ff), @@ -446,11 +495,14 @@ typedef enum { /** measure similarity ignoring leading whitespace (default) */ GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0, /** measure similarity ignoring all whitespace */ - GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 12), + GIT_DIFF_FIND_IGNORE_WHITESPACE = (1u << 12), /** measure similarity including all data */ - GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13), + GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1u << 13), /** measure similarity only by comparing SHAs (fast and cheap) */ - GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14), + GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1u << 14), + + /** do not break rewrites unless they contribute to a rename */ + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1u << 15), } git_diff_find_t; /** @@ -515,22 +567,22 @@ typedef struct { #define GIT_DIFF_FIND_OPTIONS_VERSION 1 #define GIT_DIFF_FIND_OPTIONS_INIT {GIT_DIFF_FIND_OPTIONS_VERSION} -/** @name Diff List Generator Functions +/** @name Diff Generator Functions * * These are the functions you would use to create (or destroy) a - * git_diff_list from various objects in a repository. + * git_diff from various objects in a repository. */ /**@{*/ /** - * Deallocate a diff list. + * Deallocate a diff. * - * @param diff The previously created diff list; cannot be used after free. + * @param diff The previously created diff; cannot be used after free. */ -GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); +GIT_EXTERN(void) git_diff_free(git_diff *diff); /** - * Create a diff list with the difference between two tree objects. + * Create a diff with the difference between two tree objects. * * This is equivalent to `git diff ` * @@ -539,21 +591,21 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); * pass NULL to indicate an empty tree, although it is an error to pass * NULL for both the `old_tree` and `new_tree`. * - * @param diff Output pointer to a git_diff_list pointer to be allocated. + * @param diff Output pointer to a git_diff pointer to be allocated. * @param repo The repository containing the trees. * @param old_tree A git_tree object to diff from, or NULL for empty tree. * @param new_tree A git_tree object to diff to, or NULL for empty tree. * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_tree_to_tree( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_tree *new_tree, const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Create a diff list between a tree and repository index. + * Create a diff between a tree and repository index. * * This is equivalent to `git diff --cached ` or if you pass * the HEAD tree, then like `git diff --cached`. @@ -561,21 +613,25 @@ GIT_EXTERN(int) git_diff_tree_to_tree( * The tree you pass will be used for the "old_file" side of the delta, and * the index will be used for the "new_file" side of the delta. * - * @param diff Output pointer to a git_diff_list pointer to be allocated. + * If you pass NULL for the index, then the existing index of the `repo` + * will be used. In this case, the index will be refreshed from disk + * (if it has changed) before the diff is generated. + * + * @param diff Output pointer to a git_diff pointer to be allocated. * @param repo The repository containing the tree and index. * @param old_tree A git_tree object to diff from, or NULL for empty tree. * @param index The index to diff with; repo index used if NULL. * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_tree_to_index( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Create a diff list between the repository index and the workdir directory. + * Create a diff between the repository index and the workdir directory. * * This matches the `git diff` command. See the note below on * `git_diff_tree_to_workdir` for a discussion of the difference between @@ -585,19 +641,23 @@ GIT_EXTERN(int) git_diff_tree_to_index( * The index will be used for the "old_file" side of the delta, and the * working directory will be used for the "new_file" side of the delta. * - * @param diff Output pointer to a git_diff_list pointer to be allocated. + * If you pass NULL for the index, then the existing index of the `repo` + * will be used. In this case, the index will be refreshed from disk + * (if it has changed) before the diff is generated. + * + * @param diff Output pointer to a git_diff pointer to be allocated. * @param repo The repository. * @param index The index to diff from; repo index used if NULL. * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_index_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Create a diff list between a tree and the working directory. + * Create a diff between a tree and the working directory. * * The tree you provide will be used for the "old_file" side of the delta, * and the working directory will be used for the "new_file" side. @@ -611,31 +671,51 @@ GIT_EXTERN(int) git_diff_index_to_workdir( * files in the index. It may come as a surprise, but there is no direct * equivalent in core git. * - * To emulate `git diff `, call both `git_diff_tree_to_index` and - * `git_diff_index_to_workdir`, then call `git_diff_merge` on the results. - * That will yield a `git_diff_list` that matches the git output. + * To emulate `git diff `, use `git_diff_tree_to_workdir_with_index` + * (or `git_diff_tree_to_index` and `git_diff_index_to_workdir`, then call + * `git_diff_merge` on the results). That will yield a `git_diff` that + * matches the git output. * * If this seems confusing, take the case of a file with a staged deletion * where the file has then been put back into the working dir and modified. * The tree-to-workdir diff for that file is 'modified', but core git would * show status 'deleted' since there is a pending deletion in the index. * - * @param diff A pointer to a git_diff_list pointer that will be allocated. + * @param diff A pointer to a git_diff pointer that will be allocated. * @param repo The repository containing the tree. * @param old_tree A git_tree object to diff from, or NULL for empty tree. * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_tree_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Merge one diff list into another. + * Create a diff between a tree and the working directory using index data + * to account for staged deletes, tracked files, etc. + * + * This emulates `git diff ` by diffing the tree to the index and + * the index to the working directory and blending the results into a + * single diff that includes staged deleted, etc. + * + * @param diff A pointer to a git_diff pointer that will be allocated. + * @param repo The repository containing the tree. + * @param old_tree A git_tree object to diff from, or NULL for empty tree. + * @param opts Structure with options to influence diff or NULL for defaults. + */ +GIT_EXTERN(int) git_diff_tree_to_workdir_with_index( + git_diff **diff, + git_repository *repo, + git_tree *old_tree, + const git_diff_options *opts); /**< can be NULL for defaults */ + +/** + * Merge one diff into another. * * This merges items from the "from" list into the "onto" list. The - * resulting diff list will have all items that appear in either list. + * resulting diff will have all items that appear in either list. * If an item appears in both lists, then it will be "merged" to appear * as if the old version was from the "onto" list and the new version * is from the "from" list (with the exception that if the item has a @@ -645,50 +725,117 @@ GIT_EXTERN(int) git_diff_tree_to_workdir( * @param from Diff to merge. */ GIT_EXTERN(int) git_diff_merge( - git_diff_list *onto, - const git_diff_list *from); + git_diff *onto, + const git_diff *from); /** - * Transform a diff list marking file renames, copies, etc. + * Transform a diff marking file renames, copies, etc. * - * This modifies a diff list in place, replacing old entries that look + * This modifies a diff in place, replacing old entries that look * like renames or copies with new entries reflecting those changes. * This also will, if requested, break modified files into add/remove * pairs if the amount of change is above a threshold. * - * @param diff Diff list to run detection algorithms on + * @param diff diff to run detection algorithms on * @param options Control how detection should be run, NULL for defaults * @return 0 on success, -1 on failure */ GIT_EXTERN(int) git_diff_find_similar( - git_diff_list *diff, - git_diff_find_options *options); + git_diff *diff, + const git_diff_find_options *options); + +/** + * Initialize diff options structure + * + * In most cases, you can probably just use `GIT_DIFF_OPTIONS_INIT` to + * initialize the diff options structure, but in some cases that is not + * going to work. You can call this function instead. Note that you + * must pass both a pointer to the structure to be initialized and the + * `GIT_DIFF_OPTIONS_VERSION` value from the header you compiled with. + * + * @param options Pointer to git_diff_options memory to be initialized + * @param version Should be `GIT_DIFF_OPTIONS_VERSION` + * @return 0 on success, negative on failure (such as unsupported version) + */ +GIT_EXTERN(int) git_diff_options_init( + git_diff_options *options, + unsigned int version); /**@}*/ -/** @name Diff List Processor Functions +/** @name Diff Processor Functions * - * These are the functions you apply to a diff list to process it + * These are the functions you apply to a diff to process it * or read it in some way. */ /**@{*/ /** - * Loop over all deltas in a diff list issuing callbacks. + * Query how many diff records are there in a diff. + * + * @param diff A git_diff generated by one of the above functions + * @return Count of number of deltas in the list + */ +GIT_EXTERN(size_t) git_diff_num_deltas(const git_diff *diff); + +/** + * Query how many diff deltas are there in a diff filtered by type. + * + * This works just like `git_diff_entrycount()` with an extra parameter + * that is a `git_delta_t` and returns just the count of how many deltas + * match that particular type. + * + * @param diff A git_diff generated by one of the above functions + * @param type A git_delta_t value to filter the count + * @return Count of number of deltas matching delta_t type + */ +GIT_EXTERN(size_t) git_diff_num_deltas_of_type( + const git_diff *diff, git_delta_t type); + +/** + * Return the diff delta for an entry in the diff list. + * + * The `git_delta` pointer points to internal data and you do not have + * to release it when you are done with it. It will go away when the + * `git_diff` (or any associated `git_patch`) goes away. + * + * Note that the flags on the delta related to whether it has binary + * content or not may not be set if there are no attributes set for the + * file and there has been no reason to load the file data at this point. + * For now, if you need those flags to be up to date, your only option is + * to either use `git_diff_foreach` or create a `git_patch`. + * + * @param diff Diff list object + * @param idx Index into diff list + * @return Pointer to git_diff_delta (or NULL if `idx` out of range) + */ +GIT_EXTERN(const git_diff_delta *) git_diff_get_delta( + const git_diff *diff, size_t idx); + +/** + * Check if deltas are sorted case sensitively or insensitively. + * + * @param diff diff to check + * @return 0 if case sensitive, 1 if case is ignored + */ +GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff *diff); + +/** + * Loop over all deltas in a diff issuing callbacks. * * This will iterate through all of the files described in a diff. You * should provide a file callback to learn about each file. * * The "hunk" and "line" callbacks are optional, and the text diff of the * files will only be calculated if they are not NULL. Of course, these - * callbacks will not be invoked for binary files on the diff list or for + * callbacks will not be invoked for binary files on the diff or for * files whose only changed is a file mode change. * * Returning a non-zero value from any of the callbacks will terminate * the iteration and cause this return `GIT_EUSER`. * - * @param diff A git_diff_list generated by one of the above functions. + * @param diff A git_diff generated by one of the above functions. * @param file_cb Callback function to make per file in the diff. * @param hunk_cb Optional callback to make per hunk of text diff. This * callback is called to describe a range of lines in the @@ -700,52 +847,19 @@ GIT_EXTERN(int) git_diff_find_similar( * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_foreach( - git_diff_list *diff, + git_diff *diff, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, - void *payload); - -/** - * Iterate over a diff generating text output like "git diff --name-status". - * - * Returning a non-zero value from the callbacks will terminate the - * iteration and cause this return `GIT_EUSER`. - * - * @param diff A git_diff_list generated by one of the above functions. - * @param print_cb Callback to make per line of diff text. - * @param payload Reference pointer that will be passed to your callback. - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_diff_print_compact( - git_diff_list *diff, - git_diff_data_cb print_cb, - void *payload); - -/** - * Iterate over a diff generating text output like "git diff --raw". - * - * Returning a non-zero value from the callbacks will terminate the - * iteration and cause this return `GIT_EUSER`. - * - * @param diff A git_diff_list generated by one of the above functions. - * @param print_cb Callback to make per line of diff text. - * @param payload Reference pointer that will be passed to your callback. - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_diff_print_raw( - git_diff_list *diff, - git_diff_data_cb print_cb, + git_diff_line_cb line_cb, void *payload); /** * Look up the single character abbreviation for a delta status code. * - * When you call `git_diff_print_compact` it prints single letter codes into - * the output such as 'A' for added, 'D' for deleted, 'M' for modified, etc. - * It is sometimes convenient to convert a git_delta_t value into these - * letters for your own purposes. This function does just that. By the - * way, unmodified will return a space (i.e. ' '). + * When you run `git diff --name-status` it uses single letter codes in + * the output such as 'A' for added, 'D' for deleted, 'M' for modified, + * etc. This function converts a git_delta_t value into these letters for + * your own purposes. GIT_DELTA_UNTRACKED will return a space (i.e. ' '). * * @param status The git_delta_t value to look up * @return The single character label for that code @@ -753,212 +867,34 @@ GIT_EXTERN(int) git_diff_print_raw( GIT_EXTERN(char) git_diff_status_char(git_delta_t status); /** - * Iterate over a diff generating text output like "git diff". - * - * This is a super easy way to generate a patch from a diff. + * Possible output formats for diff data + */ +typedef enum { + GIT_DIFF_FORMAT_PATCH = 1u, /**< full git diff */ + GIT_DIFF_FORMAT_PATCH_HEADER = 2u, /**< just the file headers of patch */ + GIT_DIFF_FORMAT_RAW = 3u, /**< like git diff --raw */ + GIT_DIFF_FORMAT_NAME_ONLY = 4u, /**< like git diff --name-only */ + GIT_DIFF_FORMAT_NAME_STATUS = 5u, /**< like git diff --name-status */ +} git_diff_format_t; + +/** + * Iterate over a diff generating formatted text output. * * Returning a non-zero value from the callbacks will terminate the * iteration and cause this return `GIT_EUSER`. * - * @param diff A git_diff_list generated by one of the above functions. - * @param payload Reference pointer that will be passed to your callbacks. - * @param print_cb Callback function to output lines of the diff. This - * same function will be called for file headers, hunk - * headers, and diff lines. Fortunately, you can probably - * use various GIT_DIFF_LINE constants to determine what - * text you are given. + * @param diff A git_diff generated by one of the above functions. + * @param format A git_diff_format_t value to pick the text format. + * @param print_cb Callback to make per line of diff text. + * @param payload Reference pointer that will be passed to your callback. * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ -GIT_EXTERN(int) git_diff_print_patch( - git_diff_list *diff, - git_diff_data_cb print_cb, +GIT_EXTERN(int) git_diff_print( + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb print_cb, void *payload); -/** - * Query how many diff records are there in a diff list. - * - * @param diff A git_diff_list generated by one of the above functions - * @return Count of number of deltas in the list - */ -GIT_EXTERN(size_t) git_diff_num_deltas(git_diff_list *diff); - -/** - * Query how many diff deltas are there in a diff list filtered by type. - * - * This works just like `git_diff_entrycount()` with an extra parameter - * that is a `git_delta_t` and returns just the count of how many deltas - * match that particular type. - * - * @param diff A git_diff_list generated by one of the above functions - * @param type A git_delta_t value to filter the count - * @return Count of number of deltas matching delta_t type - */ -GIT_EXTERN(size_t) git_diff_num_deltas_of_type( - git_diff_list *diff, - git_delta_t type); - -/** - * Return the diff delta and patch for an entry in the diff list. - * - * The `git_diff_patch` is a newly created object contains the text diffs - * for the delta. You have to call `git_diff_patch_free()` when you are - * done with it. You can use the patch object to loop over all the hunks - * and lines in the diff of the one delta. - * - * For an unchanged file or a binary file, no `git_diff_patch` will be - * created, the output will be set to NULL, and the `binary` flag will be - * set true in the `git_diff_delta` structure. - * - * The `git_diff_delta` pointer points to internal data and you do not have - * to release it when you are done with it. It will go away when the - * `git_diff_list` and `git_diff_patch` go away. - * - * It is okay to pass NULL for either of the output parameters; if you pass - * NULL for the `git_diff_patch`, then the text diff will not be calculated. - * - * @param patch_out Output parameter for the delta patch object - * @param delta_out Output parameter for the delta object - * @param diff Diff list object - * @param idx Index into diff list - * @return 0 on success, other value < 0 on error - */ -GIT_EXTERN(int) git_diff_get_patch( - git_diff_patch **patch_out, - const git_diff_delta **delta_out, - git_diff_list *diff, - size_t idx); - -/** - * Free a git_diff_patch object. - */ -GIT_EXTERN(void) git_diff_patch_free( - git_diff_patch *patch); - -/** - * Get the delta associated with a patch - */ -GIT_EXTERN(const git_diff_delta *) git_diff_patch_delta( - git_diff_patch *patch); - -/** - * Get the number of hunks in a patch - */ -GIT_EXTERN(size_t) git_diff_patch_num_hunks( - git_diff_patch *patch); - -/** - * Get line counts of each type in a patch. - * - * This helps imitate a diff --numstat type of output. For that purpose, - * you only need the `total_additions` and `total_deletions` values, but we - * include the `total_context` line count in case you want the total number - * of lines of diff output that will be generated. - * - * All outputs are optional. Pass NULL if you don't need a particular count. - * - * @param total_context Count of context lines in output, can be NULL. - * @param total_additions Count of addition lines in output, can be NULL. - * @param total_deletions Count of deletion lines in output, can be NULL. - * @param patch The git_diff_patch object - * @return 0 on success, <0 on error - */ -GIT_EXTERN(int) git_diff_patch_line_stats( - size_t *total_context, - size_t *total_additions, - size_t *total_deletions, - const git_diff_patch *patch); - -/** - * Get the information about a hunk in a patch - * - * Given a patch and a hunk index into the patch, this returns detailed - * information about that hunk. Any of the output pointers can be passed - * as NULL if you don't care about that particular piece of information. - * - * @param range Output pointer to git_diff_range of hunk - * @param header Output pointer to header string for hunk. Unlike the - * content pointer for each line, this will be NUL-terminated - * @param header_len Output value of characters in header string - * @param lines_in_hunk Output count of total lines in this hunk - * @param patch Input pointer to patch object - * @param hunk_idx Input index of hunk to get information about - * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error - */ -GIT_EXTERN(int) git_diff_patch_get_hunk( - const git_diff_range **range, - const char **header, - size_t *header_len, - size_t *lines_in_hunk, - git_diff_patch *patch, - size_t hunk_idx); - -/** - * Get the number of lines in a hunk. - * - * @param patch The git_diff_patch object - * @param hunk_idx Index of the hunk - * @return Number of lines in hunk or -1 if invalid hunk index - */ -GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk( - git_diff_patch *patch, - size_t hunk_idx); - -/** - * Get data about a line in a hunk of a patch. - * - * Given a patch, a hunk index, and a line index in the hunk, this - * will return a lot of details about that line. If you pass a hunk - * index larger than the number of hunks or a line index larger than - * the number of lines in the hunk, this will return -1. - * - * @param line_origin A GIT_DIFF_LINE constant from above - * @param content Pointer to content of diff line, not NUL-terminated - * @param content_len Number of characters in content - * @param old_lineno Line number in old file or -1 if line is added - * @param new_lineno Line number in new file or -1 if line is deleted - * @param patch The patch to look in - * @param hunk_idx The index of the hunk - * @param line_of_hunk The index of the line in the hunk - * @return 0 on success, <0 on failure - */ -GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( - char *line_origin, - const char **content, - size_t *content_len, - int *old_lineno, - int *new_lineno, - git_diff_patch *patch, - size_t hunk_idx, - size_t line_of_hunk); - -/** - * Serialize the patch to text via callback. - * - * Returning a non-zero value from the callback will terminate the iteration - * and cause this return `GIT_EUSER`. - * - * @param patch A git_diff_patch representing changes to one file - * @param print_cb Callback function to output lines of the patch. Will be - * called for file headers, hunk headers, and diff lines. - * @param payload Reference pointer that will be passed to your callbacks. - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_diff_patch_print( - git_diff_patch *patch, - git_diff_data_cb print_cb, - void *payload); - -/** - * Get the content of a patch as a single diff text. - * - * @param string Allocated string; caller must free. - * @param patch A git_diff_patch representing changes to one file - * @return 0 on success, <0 on failure. - */ -GIT_EXTERN(int) git_diff_patch_to_str( - char **string, - git_diff_patch *patch); - /**@}*/ @@ -1001,33 +937,9 @@ GIT_EXTERN(int) git_diff_blobs( const git_diff_options *options, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *payload); -/** - * Directly generate a patch from the difference between two blobs. - * - * This is just like `git_diff_blobs()` except it generates a patch object - * for the difference instead of directly making callbacks. You can use the - * standard `git_diff_patch` accessor functions to read the patch data, and - * you must call `git_diff_patch_free()` on the patch when done. - * - * @param out The generated patch; NULL on error - * @param old_blob Blob for old side of diff, or NULL for empty blob - * @param old_as_path Treat old blob as if it had this filename; can be NULL - * @param new_blob Blob for new side of diff, or NULL for empty blob - * @param new_as_path Treat new blob as if it had this filename; can be NULL - * @param opts Options for diff, or NULL for default options - * @return 0 on success or error code < 0 - */ -GIT_EXTERN(int) git_diff_patch_from_blobs( - git_diff_patch **out, - const git_blob *old_blob, - const char *old_as_path, - const git_blob *new_blob, - const char *new_as_path, - const git_diff_options *opts); - /** * Directly run a diff between a blob and a buffer. * @@ -1048,7 +960,7 @@ GIT_EXTERN(int) git_diff_patch_from_blobs( * @param options Options for diff, or NULL for default options * @param file_cb Callback for "file"; made once if there is a diff; can be NULL * @param hunk_cb Callback for each hunk in diff; can be NULL - * @param data_cb Callback for each line in diff; can be NULL + * @param line_cb Callback for each line in diff; can be NULL * @param payload Payload passed to each callback function * @return 0 on success, GIT_EUSER on non-zero callback return, or error code */ @@ -1061,35 +973,9 @@ GIT_EXTERN(int) git_diff_blob_to_buffer( const git_diff_options *options, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb line_cb, void *payload); -/** - * Directly generate a patch from the difference between a blob and a buffer. - * - * This is just like `git_diff_blob_to_buffer()` except it generates a patch - * object for the difference instead of directly making callbacks. You can - * use the standard `git_diff_patch` accessor functions to read the patch - * data, and you must call `git_diff_patch_free()` on the patch when done. - * - * @param out The generated patch; NULL on error - * @param old_blob Blob for old side of diff, or NULL for empty blob - * @param old_as_path Treat old blob as if it had this filename; can be NULL - * @param buffer Raw data for new side of diff, or NULL for empty - * @param buffer_len Length of raw data for new side of diff - * @param buffer_as_path Treat buffer as if it had this filename; can be NULL - * @param opts Options for diff, or NULL for default options - * @return 0 on success or error code < 0 - */ -GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer( - git_diff_patch **out, - const git_blob *old_blob, - const char *old_as_path, - const char *buffer, - size_t buffer_len, - const char *buffer_as_path, - const git_diff_options *opts); - GIT_END_DECL diff --git a/include/git2/errors.h b/include/git2/errors.h index 2032a436a..be7a31d8e 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -8,6 +8,7 @@ #define INCLUDE_git_errors_h__ #include "common.h" +#include "buffer.h" /** * @file git2/errors.h @@ -27,11 +28,12 @@ typedef enum { GIT_EBUFS = -6, GIT_EUSER = -7, GIT_EBAREREPO = -8, - GIT_EORPHANEDHEAD = -9, + GIT_EUNBORNBRANCH = -9, GIT_EUNMERGED = -10, GIT_ENONFASTFORWARD = -11, GIT_EINVALIDSPEC = -12, GIT_EMERGECONFLICT = -13, + GIT_ELOCKED = -14, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, @@ -44,6 +46,7 @@ typedef struct { /** Error classes */ typedef enum { + GITERR_NONE = 0, GITERR_NOMEMORY, GITERR_OS, GITERR_INVALID, @@ -66,6 +69,8 @@ typedef enum { GITERR_CHECKOUT, GITERR_FETCHHEAD, GITERR_MERGE, + GITERR_SSH, + GITERR_FILTER, } git_error_t; /** @@ -81,6 +86,18 @@ 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. * diff --git a/include/git2/filter.h b/include/git2/filter.h new file mode 100644 index 000000000..f96b6766b --- /dev/null +++ b/include/git2/filter.h @@ -0,0 +1,142 @@ +/* + * 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_git_filter_h__ +#define INCLUDE_git_filter_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "buffer.h" + +/** + * @file git2/filter.h + * @brief Git filter APIs + * + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Filters are applied in one of two directions: smudging - which is + * exporting a file from the Git object database to the working directory, + * and cleaning - which is importing a file from the working directory to + * the Git object database. These values control which direction of + * change is being applied. + */ +typedef enum { + GIT_FILTER_TO_WORKTREE = 0, + GIT_FILTER_SMUDGE = GIT_FILTER_TO_WORKTREE, + GIT_FILTER_TO_ODB = 1, + GIT_FILTER_CLEAN = GIT_FILTER_TO_ODB, +} git_filter_mode_t; + +/** + * A filter that can transform file data + * + * This represents a filter that can be used to transform or even replace + * file data. Libgit2 includes one built in filter and it is possible to + * write your own (see git2/sys/filter.h for information on that). + * + * The two builtin filters are: + * + * * "crlf" which uses the complex rules with the "text", "eol", and + * "crlf" file attributes to decide how to convert between LF and CRLF + * line endings + * * "ident" which replaces "$Id$" in a blob with "$Id: $" upon + * checkout and replaced "$Id: $" with "$Id$" on checkin. + */ +typedef struct git_filter git_filter; + +/** + * List of filters to be applied + * + * This represents a list of filters to be applied to a file / blob. You + * can build the list with one call, apply it with another, and dispose it + * with a third. In typical usage, there are not many occasions where a + * git_filter_list is needed directly since the library will generally + * handle conversions for you, but it can be convenient to be able to + * build and apply the list sometimes. + */ +typedef struct git_filter_list git_filter_list; + +/** + * Load the filter list for a given path. + * + * This will return 0 (success) but set the output git_filter_list to NULL + * if no filters are requested for the given file. + * + * @param filters Output newly created git_filter_list (or NULL) + * @param repo Repository object that contains `path` + * @param blob The blob to which the filter will be applied (if known) + * @param path Relative path of the file to be filtered + * @param mode Filtering direction (WT->ODB or ODB->WT) + * @return 0 on success (which could still return NULL if no filters are + * needed for the requested file), <0 on error + */ +GIT_EXTERN(int) git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode); + +/** + * Apply filter list to a data buffer. + * + * See `git2/buffer.h` for background on `git_buf` objects. + * + * If the `in` buffer holds data allocated by libgit2 (i.e. `in->asize` is + * not zero), then it will be overwritten when applying the filters. If + * not, then it will be left untouched. + * + * If there are no filters to apply (or `filters` is NULL), then the `out` + * buffer will reference the `in` buffer data (with `asize` set to zero) + * instead of allocating data. This keeps allocations to a minimum, but + * it means you have to be careful about freeing the `in` data since `out` + * may be pointing to it! + * + * @param out Buffer to store the result of the filtering + * @param filters A loaded git_filter_list (or NULL) + * @param in Buffer containing the data to filter + * @return 0 on success, an error code otherwise + */ +GIT_EXTERN(int) git_filter_list_apply_to_data( + git_buf *out, + git_filter_list *filters, + git_buf *in); + +/** + * Apply filter list to the contents of a file on disk + */ +GIT_EXTERN(int) git_filter_list_apply_to_file( + git_buf *out, + git_filter_list *filters, + git_repository *repo, + const char *path); + +/** + * Apply filter list to the contents of a blob + */ +GIT_EXTERN(int) git_filter_list_apply_to_blob( + git_buf *out, + git_filter_list *filters, + git_blob *blob); + +/** + * Free a git_filter_list + * + * @param filters A git_filter_list created by `git_filter_list_load` + */ +GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters); + + +GIT_END_DECL + +/** @} */ + +#endif diff --git a/include/git2/index.h b/include/git2/index.h index 51694aded..a60db370a 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -120,9 +120,9 @@ typedef struct git_index_entry { /** Capabilities of system that affect index actions. */ typedef enum { - GIT_INDEXCAP_IGNORE_CASE = 1, - GIT_INDEXCAP_NO_FILEMODE = 2, - GIT_INDEXCAP_NO_SYMLINKS = 4, + GIT_INDEXCAP_IGNORE_CASE = 1u, + GIT_INDEXCAP_NO_FILEMODE = 2u, + GIT_INDEXCAP_NO_SYMLINKS = 4u, GIT_INDEXCAP_FROM_OWNER = ~0u } git_indexcap_t; @@ -138,6 +138,14 @@ 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 + /** @name Index File Functions * * These functions work on the index file itself. @@ -214,13 +222,23 @@ GIT_EXTERN(unsigned int) git_index_caps(const git_index *index); GIT_EXTERN(int) git_index_set_caps(git_index *index, unsigned int caps); /** - * Update the contents of an existing index object in memory - * by reading from the hard disk. + * Update the contents of an existing index object in memory by reading + * from the hard disk. + * + * If `force` is true, this performs a "hard" read that discards in-memory + * changes and always reloads the on-disk index data. If there is no + * on-disk version, the index will be cleared. + * + * If `force` is false, this does a "soft" read that reloads the index + * data from disk only if it has changed since the last time it was + * loaded. Purely in-memory index data will be untouched. Be aware: if + * there are changes on disk, unwritten in-memory changes are discarded. * * @param index an existing index object + * @param force if true, always reload, vs. only read if file has changed * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read(git_index *index); +GIT_EXTERN(int) git_index_read(git_index *index, int force); /** * Write an existing index object from memory back to disk @@ -231,6 +249,14 @@ GIT_EXTERN(int) git_index_read(git_index *index); */ GIT_EXTERN(int) git_index_write(git_index *index); +/** + * Get the full path to the index file on disk. + * + * @param index an existing index object + * @return path to index file or NULL for in-memory index + */ +GIT_EXTERN(const char *) git_index_path(git_index *index); + /** * Read a tree into the index file with stats * diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 4db072c9b..e4c03ad06 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -13,19 +13,25 @@ GIT_BEGIN_DECL -typedef struct git_indexer_stream git_indexer_stream; +typedef struct git_indexer git_indexer; /** - * Create a new streaming indexer instance + * Create a new indexer instance * * @param out where to store the indexer instance * @param path to the directory where the packfile should be stored + * @param mode permissions to use creating packfile or 0 for defaults + * @param odb object database from which to read base objects when + * fixing thin packs. Pass NULL if no thin pack is expected (an error + * will be returned if there are bases missing) * @param progress_cb function to call with progress information * @param progress_cb_payload payload for the progress callback */ -GIT_EXTERN(int) git_indexer_stream_new( - git_indexer_stream **out, +GIT_EXTERN(int) git_indexer_new( + git_indexer **out, const char *path, + unsigned int mode, + git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_cb_payload); @@ -37,7 +43,7 @@ GIT_EXTERN(int) git_indexer_stream_new( * @param size the size of the data in bytes * @param stats stat storage */ -GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats); +GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats); /** * Finalize the pack and index @@ -46,7 +52,7 @@ GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data * * @param idx the indexer */ -GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats); +GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_transfer_progress *stats); /** * Get the packfile's hash @@ -56,14 +62,14 @@ GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfe * * @param idx the indexer instance */ -GIT_EXTERN(const git_oid *) git_indexer_stream_hash(const git_indexer_stream *idx); +GIT_EXTERN(const git_oid *) git_indexer_hash(const git_indexer *idx); /** * Free the indexer and its resources * * @param idx the indexer to free */ -GIT_EXTERN(void) git_indexer_stream_free(git_indexer_stream *idx); +GIT_EXTERN(void) git_indexer_free(git_indexer *idx); GIT_END_DECL diff --git a/include/git2/merge.h b/include/git2/merge.h index cef6f775b..3354fbeab 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -7,11 +7,11 @@ #ifndef INCLUDE_git_merge_h__ #define INCLUDE_git_merge_h__ -#include "git2/common.h" -#include "git2/types.h" -#include "git2/oid.h" -#include "git2/checkout.h" -#include "git2/index.h" +#include "common.h" +#include "types.h" +#include "oid.h" +#include "checkout.h" +#include "index.h" /** * @file git2/merge.h @@ -65,6 +65,29 @@ typedef struct { #define GIT_MERGE_TREE_OPTS_INIT {GIT_MERGE_TREE_OPTS_VERSION} +/** + * Option flags for `git_merge`. + * + * GIT_MERGE_NO_FASTFORWARD - Do not fast-forward. + */ +typedef enum { + GIT_MERGE_NO_FASTFORWARD = 1, + GIT_MERGE_FASTFORWARD_ONLY = 2, +} git_merge_flags_t; + +typedef struct { + unsigned int version; + + git_merge_flags_t merge_flags; + git_merge_tree_opts merge_tree_opts; + + git_checkout_opts checkout_opts; +} git_merge_opts; + +#define GIT_MERGE_OPTS_VERSION 1 +#define GIT_MERGE_OPTS_INIT {GIT_MERGE_OPTS_VERSION, 0, GIT_MERGE_TREE_OPTS_INIT, GIT_CHECKOUT_OPTS_INIT} + + /** * Find a merge base between two commits * @@ -85,15 +108,15 @@ GIT_EXTERN(int) git_merge_base( * * @param out the OID of a merge base considering all the commits * @param repo the repository where the commits exist - * @param input_array oids of the commits * @param length The number of commits in the provided `input_array` + * @param input_array oids of the commits * @return Zero on success; GIT_ENOTFOUND or -1 on failure. */ GIT_EXTERN(int) git_merge_base_many( git_oid *out, git_repository *repo, - const git_oid input_array[], - size_t length); + size_t length, + const git_oid input_array[]); /** * Creates a `git_merge_head` from the given reference @@ -168,6 +191,43 @@ GIT_EXTERN(int) git_merge_trees( const git_tree *their_tree, const git_merge_tree_opts *opts); +/** + * Merges the given commits into HEAD, producing a new commit. + * + * @param out the results of the merge + * @param repo the repository to merge + * @param merge_heads the heads to merge into + * @param merge_heads_len the number of heads to merge + * @param flags merge flags + */ +GIT_EXTERN(int) git_merge( + git_merge_result **out, + git_repository *repo, + const git_merge_head **their_heads, + size_t their_heads_len, + const git_merge_opts *opts); + +/** + * Returns true if a merge is up-to-date (we were asked to merge the target + * into itself.) + */ +GIT_EXTERN(int) git_merge_result_is_uptodate(git_merge_result *merge_result); + +/** + * Returns true if a merge is eligible for fastforward + */ +GIT_EXTERN(int) git_merge_result_is_fastforward(git_merge_result *merge_result); + +/** + * Gets the fast-forward OID if the merge was a fastforward. + * + * @param out the OID of the fast-forward + * @param merge_result the results of the merge + */ +GIT_EXTERN(int) git_merge_result_fastforward_oid(git_oid *out, git_merge_result *merge_result); + +GIT_EXTERN(void) git_merge_result_free(git_merge_result *merge_result); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/notes.h b/include/git2/notes.h index 7382904ad..76361633b 100644 --- a/include/git2/notes.h +++ b/include/git2/notes.h @@ -99,7 +99,7 @@ GIT_EXTERN(int) git_note_read( /** * Get the note message * - * @param note + * @param note the note * @return the note message */ GIT_EXTERN(const char *) git_note_message(const git_note *note); @@ -108,7 +108,7 @@ GIT_EXTERN(const char *) git_note_message(const git_note *note); /** * Get the note object OID * - * @param note + * @param note the note * @return the note object OID */ GIT_EXTERN(const git_oid *) git_note_oid(const git_note *note); diff --git a/include/git2/object.h b/include/git2/object.h index b91b04dba..c40631fa6 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -36,7 +36,7 @@ GIT_BEGIN_DECL * @param repo the repository to look up the object * @param id the unique identifier for the object * @param type the type of the object - * @return a reference to the object + * @return 0 or an error code */ GIT_EXTERN(int) git_object_lookup( git_object **object, @@ -78,6 +78,23 @@ GIT_EXTERN(int) git_object_lookup_prefix( size_t len, git_otype type); + +/** + * Lookup an object that represents a tree entry. + * + * @param out buffer that receives a pointer to the object (which must be freed + * by the caller) + * @param treeish root object that can be peeled to a tree + * @param path relative path from the root object to the desired object + * @param type type of object desired + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_object_lookup_bypath( + git_object **out, + const git_object *treeish, + const char *path, + git_otype type); + /** * Get the id (SHA1) of a repository object * diff --git a/include/git2/odb.h b/include/git2/odb.h index b64436c4d..ad56384f0 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -120,7 +120,7 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * @param db database to search for the object in. * @param short_id a prefix of the id of the object to read. * @param len the length of the prefix - * @return + * @return * - 0 if the object was read; * - GIT_ENOTFOUND if the object is not in the database. * - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix) @@ -219,18 +219,12 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size * The type and final length of the object must be specified * when opening the stream. * - * The returned stream will be of type `GIT_STREAM_WRONLY` and - * will have the following methods: + * The returned stream will be of type `GIT_STREAM_WRONLY`, and it + * won't be effective until `git_odb_stream_finalize_write` is called + * and returns without an error * - * - stream->write: write `n` bytes into the stream - * - stream->finalize_write: close the stream and store the object in - * the odb - * - stream->free: free the stream - * - * The streaming write won't be effective until `stream->finalize_write` - * is called and returns without an error - * - * The stream must always be free'd or will leak memory. + * The stream must always be freed when done with `git_odb_stream_free` or + * will leak memory. * * @see git_odb_stream * @@ -242,6 +236,48 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size */ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type); +/** + * Write to an odb stream + * + * This method will fail if the total number of received bytes exceeds the + * size declared with `git_odb_open_wstream()` + * + * @param stream the stream + * @param buffer the data to write + * @param len the buffer's length + * @return 0 if the write succeeded; error code otherwise + */ +GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len); + +/** + * Finish writing to an odb stream + * + * The object will take its final name and will be available to the + * odb. + * + * This method will fail if the total number of received bytes + * differs from the size declared with `git_odb_open_wstream()` + * + * @param out pointer to store the resulting object's id + * @param stream the stream + * @return 0 on success; an error code otherwise + */ +GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream); + +/** + * Read from an odb stream + * + * Most backends don't implement streaming reads + */ +GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len); + +/** + * Free an odb stream + * + * @param stream the stream to free + */ +GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream); + /** * Open a stream to read an object from the ODB * @@ -321,6 +357,20 @@ GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_oty */ GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type); +/** + * Create a copy of an odb_object + * + * The returned copy must be manually freed with `git_odb_object_free`. + * Note that because of an implementation detail, the returned copy will be + * the same pointer as `source`: the object is internally refcounted, so the + * copy still needs to be freed twice. + * + * @param dest pointer where to store the copy + * @param source object to copy + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_object_dup(git_odb_object **dest, git_odb_object *source); + /** * Close an ODB object * diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index af1e3e5b9..4d772cab9 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -7,8 +7,8 @@ #ifndef INCLUDE_git_odb_backend_h__ #define INCLUDE_git_odb_backend_h__ -#include "git2/common.h" -#include "git2/types.h" +#include "common.h" +#include "types.h" /** * @file git2/backend.h @@ -40,10 +40,18 @@ GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_ * @param objects_dir the Git repository's objects directory * @param compression_level zlib compression level to use * @param do_fsync whether to do an fsync() after writing (currently ignored) + * @param dir_mode permissions to use creating a directory or 0 for defaults + * @param file_mode permissions to use creating a file or 0 for defaults * * @return 0 or an error code */ -GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **out, const char *objects_dir, int compression_level, int do_fsync); +GIT_EXTERN(int) git_odb_backend_loose( + git_odb_backend **out, + const char *objects_dir, + int compression_level, + int do_fsync, + unsigned int dir_mode, + unsigned int file_mode); /** * Create a backend out of a single packfile @@ -65,14 +73,50 @@ typedef enum { GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), } git_odb_stream_t; -/** A stream to read/write from a backend */ +/** + * A stream to read/write from a backend. + * + * This represents a stream of data being written to or read from a + * backend. When writing, the frontend functions take care of + * calculating the object's id and all `finalize_write` needs to do is + * store the object with the id it is passed. + */ struct git_odb_stream { git_odb_backend *backend; unsigned int mode; + void *hash_ctx; + size_t declared_size; + size_t received_bytes; + + /** + * Write at most `len` bytes into `buffer` and advance the stream. + */ int (*read)(git_odb_stream *stream, char *buffer, size_t len); + + /** + * Write `len` bytes from `buffer` into the stream. + */ int (*write)(git_odb_stream *stream, const char *buffer, size_t len); - int (*finalize_write)(git_oid *oid_p, git_odb_stream *stream); + + /** + * Store the contents of the stream as an object with the id + * specified in `oid`. + * + * This method might not be invoked if: + * - an error occurs earlier with the `write` callback, + * - the object referred to by `oid` already exists in any backend, or + * - the final number of received bytes differs from the size declared + * with `git_odb_open_wstream()` + */ + int (*finalize_write)(git_odb_stream *stream, const git_oid *oid); + + /** + * Free the stream's memory. + * + * This method might be called without a call to `finalize_write` if + * an error occurs or if the object is already present in the ODB. + */ void (*free)(git_odb_stream *stream); }; @@ -80,7 +124,7 @@ struct git_odb_stream { struct git_odb_writepack { git_odb_backend *backend; - int (*add)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats); + int (*append)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats); int (*commit)(git_odb_writepack *writepack, git_transfer_progress *stats); void (*free)(git_odb_writepack *writepack); }; diff --git a/include/git2/oid.h b/include/git2/oid.h index 662338d93..384b656d7 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -188,8 +188,7 @@ GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len); * * @param id oid structure. * @param str input hex string of an object id. - * @return GIT_ENOTOID if str is not a valid hex string, - * 0 in case of a match, GIT_ERROR otherwise. + * @return 0 in case of a match, -1 otherwise. */ GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str); @@ -241,13 +240,13 @@ GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length); * or freed. * * For performance reasons, there is a hard-limit of how many - * OIDs can be added to a single set (around ~22000, assuming + * OIDs can be added to a single set (around ~32000, assuming * a mostly randomized distribution), which should be enough * for any kind of program, and keeps the algorithm fast and * memory-efficient. * * Attempting to add more than those OIDs will result in a - * GIT_ENOMEM error + * GITERR_INVALID error * * @param os a `git_oid_shorten` instance * @param text_id an OID in text form diff --git a/include/git2/pack.h b/include/git2/pack.h index cc1f48add..88a2716bb 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -38,13 +38,21 @@ * `git_packbuilder_set_threads` can be used to adjust the number of * threads used for the process. * - * See tests-clar/pack/packbuilder.c for an example. + * See tests/pack/packbuilder.c for an example. * * @ingroup Git * @{ */ GIT_BEGIN_DECL +/** + * Stages that are reported by the packbuilder progress callback. + */ +typedef enum { + GIT_PACKBUILDER_ADDING_OBJECTS = 0, + GIT_PACKBUILDER_DELTAFICATION = 1, +} git_packbuilder_stage_t; + /** * Initialize a new packbuilder * @@ -111,6 +119,7 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid * * @param pb The packbuilder * @param path to the directory where the packfile and index should be stored + * @param mode permissions to use creating a packfile or 0 for defaults * @param progress_cb function to call with progress information from the indexer (optional) * @param progress_cb_payload payload for the progress callback (optional) * @@ -119,9 +128,20 @@ GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid GIT_EXTERN(int) git_packbuilder_write( git_packbuilder *pb, const char *path, + unsigned int mode, git_transfer_progress_callback progress_cb, void *progress_cb_payload); +/** +* Get the packfile's hash +* +* A packfile's name is derived from the sorted hashing of all object +* names. This is only correct after the packfile has been written. +* +* @param pb The packbuilder object +*/ +GIT_EXTERN(const git_oid *) git_packbuilder_hash(git_packbuilder *pb); + typedef int (*git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload); /** * Create the new pack and pass each object to the callback @@ -137,7 +157,7 @@ GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_for * Get the total number of objects the packbuilder will write out * * @param pb the packbuilder - * @return + * @return the number of objects in the packfile */ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); @@ -145,10 +165,32 @@ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); * Get the number of objects the packbuilder has already written out * * @param pb the packbuilder - * @return + * @return the number of objects which have already been written */ GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb); +/** Packbuilder progress notification function */ +typedef int (*git_packbuilder_progress)( + int stage, + unsigned int current, + unsigned int total, + void *payload); + +/** + * Set the callbacks for a packbuilder + * + * @param pb The packbuilder object + * @param progress_cb Function to call with progress information during + * pack building. Be aware that this is called inline with pack building + * operations, so performance may be affected. + * @param progress_cb_payload Payload for progress callback. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_packbuilder_set_callbacks( + git_packbuilder *pb, + git_packbuilder_progress progress_cb, + void *progress_cb_payload); + /** * Free the packbuilder and all associated data * diff --git a/include/git2/patch.h b/include/git2/patch.h new file mode 100644 index 000000000..6a6ad92d7 --- /dev/null +++ b/include/git2/patch.h @@ -0,0 +1,250 @@ +/* + * 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_git_patch_h__ +#define INCLUDE_git_patch_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "diff.h" + +/** + * @file git2/patch.h + * @brief Patch handling routines. + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * The diff patch is used to store all the text diffs for a delta. + * + * You can easily loop over the content of patches and get information about + * them. + */ +typedef struct git_patch git_patch; + +/** + * Return the diff delta and patch for an entry in the diff list. + * + * The `git_patch` is a newly created object contains the text diffs + * for the delta. You have to call `git_patch_free()` when you are + * done with it. You can use the patch object to loop over all the hunks + * and lines in the diff of the one delta. + * + * For an unchanged file or a binary file, no `git_patch` will be + * created, the output will be set to NULL, and the `binary` flag will be + * set true in the `git_diff_delta` structure. + * + * The `git_diff_delta` pointer points to internal data and you do not have + * to release it when you are done with it. It will go away when the + * `git_diff` and `git_patch` go away. + * + * It is okay to pass NULL for either of the output parameters; if you pass + * NULL for the `git_patch`, then the text diff will not be calculated. + * + * @param out Output parameter for the delta patch object + * @param diff Diff list object + * @param idx Index into diff list + * @return 0 on success, other value < 0 on error + */ +GIT_EXTERN(int) git_patch_from_diff( + git_patch **out, git_diff *diff, size_t idx); + +/** + * Directly generate a patch from the difference between two blobs. + * + * This is just like `git_diff_blobs()` except it generates a patch object + * for the difference instead of directly making callbacks. You can use the + * standard `git_patch` accessor functions to read the patch data, and + * you must call `git_patch_free()` on the patch when done. + * + * @param out The generated patch; NULL on error + * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL + * @param new_blob Blob for new side of diff, or NULL for empty blob + * @param new_as_path Treat new blob as if it had this filename; can be NULL + * @param opts Options for diff, or NULL for default options + * @return 0 on success or error code < 0 + */ +GIT_EXTERN(int) git_patch_from_blobs( + git_patch **out, + const git_blob *old_blob, + const char *old_as_path, + const git_blob *new_blob, + const char *new_as_path, + const git_diff_options *opts); + +/** + * Directly generate a patch from the difference between a blob and a buffer. + * + * This is just like `git_diff_blob_to_buffer()` except it generates a patch + * object for the difference instead of directly making callbacks. You can + * use the standard `git_patch` accessor functions to read the patch + * data, and you must call `git_patch_free()` on the patch when done. + * + * @param out The generated patch; NULL on error + * @param old_blob Blob for old side of diff, or NULL for empty blob + * @param old_as_path Treat old blob as if it had this filename; can be NULL + * @param buffer Raw data for new side of diff, or NULL for empty + * @param buffer_len Length of raw data for new side of diff + * @param buffer_as_path Treat buffer as if it had this filename; can be NULL + * @param opts Options for diff, or NULL for default options + * @return 0 on success or error code < 0 + */ +GIT_EXTERN(int) git_patch_from_blob_and_buffer( + git_patch **out, + const git_blob *old_blob, + const char *old_as_path, + const char *buffer, + size_t buffer_len, + const char *buffer_as_path, + const git_diff_options *opts); + +/** + * Free a git_patch object. + */ +GIT_EXTERN(void) git_patch_free(git_patch *patch); + +/** + * Get the delta associated with a patch + */ +GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(git_patch *patch); + +/** + * Get the number of hunks in a patch + */ +GIT_EXTERN(size_t) git_patch_num_hunks(git_patch *patch); + +/** + * Get line counts of each type in a patch. + * + * This helps imitate a diff --numstat type of output. For that purpose, + * you only need the `total_additions` and `total_deletions` values, but we + * include the `total_context` line count in case you want the total number + * of lines of diff output that will be generated. + * + * All outputs are optional. Pass NULL if you don't need a particular count. + * + * @param total_context Count of context lines in output, can be NULL. + * @param total_additions Count of addition lines in output, can be NULL. + * @param total_deletions Count of deletion lines in output, can be NULL. + * @param patch The git_patch object + * @return 0 on success, <0 on error + */ +GIT_EXTERN(int) git_patch_line_stats( + size_t *total_context, + size_t *total_additions, + size_t *total_deletions, + const git_patch *patch); + +/** + * Get the information about a hunk in a patch + * + * Given a patch and a hunk index into the patch, this returns detailed + * information about that hunk. Any of the output pointers can be passed + * as NULL if you don't care about that particular piece of information. + * + * @param out Output pointer to git_diff_hunk of hunk + * @param lines_in_hunk Output count of total lines in this hunk + * @param patch Input pointer to patch object + * @param hunk_idx Input index of hunk to get information about + * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error + */ +GIT_EXTERN(int) git_patch_get_hunk( + const git_diff_hunk **out, + size_t *lines_in_hunk, + git_patch *patch, + size_t hunk_idx); + +/** + * Get the number of lines in a hunk. + * + * @param patch The git_patch object + * @param hunk_idx Index of the hunk + * @return Number of lines in hunk or -1 if invalid hunk index + */ +GIT_EXTERN(int) git_patch_num_lines_in_hunk( + git_patch *patch, + size_t hunk_idx); + +/** + * Get data about a line in a hunk of a patch. + * + * Given a patch, a hunk index, and a line index in the hunk, this + * will return a lot of details about that line. If you pass a hunk + * index larger than the number of hunks or a line index larger than + * the number of lines in the hunk, this will return -1. + * + * @param out The git_diff_line data for this line + * @param patch The patch to look in + * @param hunk_idx The index of the hunk + * @param line_of_hunk The index of the line in the hunk + * @return 0 on success, <0 on failure + */ +GIT_EXTERN(int) git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, + size_t hunk_idx, + size_t line_of_hunk); + +/** + * Look up size of patch diff data in bytes + * + * This returns the raw size of the patch data. This only includes the + * actual data from the lines of the diff, not the file or hunk headers. + * + * If you pass `include_context` as true (non-zero), this will be the size + * of all of the diff output; if you pass it as false (zero), this will + * only include the actual changed lines (as if `context_lines` was 0). + * + * @param patch A git_patch representing changes to one file + * @param include_context Include context lines in size if non-zero + * @param include_hunk_headers Include hunk header lines if non-zero + * @param include_file_headers Include file header lines if non-zero + * @return The number of bytes of data + */ +GIT_EXTERN(size_t) git_patch_size( + git_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers); + +/** + * Serialize the patch to text via callback. + * + * Returning a non-zero value from the callback will terminate the iteration + * and cause this return `GIT_EUSER`. + * + * @param patch A git_patch representing changes to one file + * @param print_cb Callback function to output lines of the patch. Will be + * called for file headers, hunk headers, and diff lines. + * @param payload Reference pointer that will be passed to your callbacks. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, + void *payload); + +/** + * Get the content of a patch as a single diff text. + * + * @param string Allocated string; caller must free. + * @param patch A git_patch representing changes to one file + * @return 0 on success, <0 on failure. + */ +GIT_EXTERN(int) git_patch_to_str( + char **string, + git_patch *patch); + + +GIT_END_DECL + +/**@}*/ + +#endif diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h new file mode 100644 index 000000000..2fb0bb716 --- /dev/null +++ b/include/git2/pathspec.h @@ -0,0 +1,260 @@ +/* + * 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_git_pathspec_h__ +#define INCLUDE_git_pathspec_h__ + +#include "common.h" +#include "types.h" +#include "strarray.h" +#include "diff.h" + +/** + * Compiled pathspec + */ +typedef struct git_pathspec git_pathspec; + +/** + * List of filenames matching a pathspec + */ +typedef struct git_pathspec_match_list git_pathspec_match_list; + +/** + * Options controlling how pathspec match should be executed + * + * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise + * match will use native case sensitivity of platform filesystem + * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise + * match will use native case sensitivity of platform filesystem + * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple + * string comparison for matching + * - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error + * code GIT_ENOTFOUND if no matches are found; otherwise no matches is + * still success (return 0) but `git_pathspec_match_list_entrycount` + * will indicate 0 matches. + * - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list` + * should track which patterns matched which files so that at the end of + * the match we can identify patterns that did not match any files. + * - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list` + * does not need to keep the actual matching filenames. Use this to + * just test if there were any matches at all or in combination with + * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec. + */ +typedef enum { + GIT_PATHSPEC_DEFAULT = 0, + GIT_PATHSPEC_IGNORE_CASE = (1u << 0), + GIT_PATHSPEC_USE_CASE = (1u << 1), + GIT_PATHSPEC_NO_GLOB = (1u << 2), + GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3), + GIT_PATHSPEC_FIND_FAILURES = (1u << 4), + GIT_PATHSPEC_FAILURES_ONLY = (1u << 5), +} git_pathspec_flag_t; + +/** + * Compile a pathspec + * + * @param out Output of the compiled pathspec + * @param pathspec A git_strarray of the paths to match + * @return 0 on success, <0 on failure + */ +GIT_EXTERN(int) git_pathspec_new( + git_pathspec **out, const git_strarray *pathspec); + +/** + * Free a pathspec + * + * @param ps The compiled pathspec + */ +GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps); + +/** + * Try to match a path against a pathspec + * + * Unlike most of the other pathspec matching functions, this will not + * fall back on the native case-sensitivity for your platform. You must + * explicitly pass flags to control case sensitivity or else this will + * fall back on being case sensitive. + * + * @param ps The compiled pathspec + * @param flags Combination of git_pathspec_flag_t options to control match + * @param path The pathname to attempt to match + * @return 1 is path matches spec, 0 if it does not + */ +GIT_EXTERN(int) git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path); + +/** + * Match a pathspec against the working directory of a repository. + * + * This matches the pathspec against the current files in the working + * directory of the repository. It is an error to invoke this on a bare + * repo. This handles git ignores (i.e. ignored files will not be + * considered to match the `pathspec` unless the file is tracked in the + * index). + * + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param repo The repository in which to match; bare repo is an error + * @param flags Combination of git_pathspec_flag_t options to control match + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag was given + */ +GIT_EXTERN(int) git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps); + +/** + * Match a pathspec against entries in an index. + * + * This matches the pathspec against the files in the repository index. + * + * NOTE: At the moment, the case sensitivity of this match is controlled + * by the current case-sensitivity of the index object itself and the + * USE_CASE and IGNORE_CASE flags will have no effect. This behavior will + * be corrected in a future release. + * + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param index The index to match against + * @param flags Combination of git_pathspec_flag_t options to control match + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps); + +/** + * Match a pathspec against files in a tree. + * + * This matches the pathspec against the files in the given tree. + * + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param tree The root-level tree to match against + * @param flags Combination of git_pathspec_flag_t options to control match + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps); + +/** + * Match a pathspec against files in a diff list. + * + * This matches the pathspec against the files in the given diff list. + * + * If `out` is not NULL, this returns a `git_patchspec_match_list`. That + * contains the list of all matched filenames (unless you pass the + * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of + * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES` + * flag). You must call `git_pathspec_match_list_free()` on this object. + * + * @param out Output list of matches; pass NULL to just get return value + * @param diff A generated diff list + * @param flags Combination of git_pathspec_flag_t options to control match + * @param ps Pathspec to be matched + * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and + * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used + */ +GIT_EXTERN(int) git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff *diff, + uint32_t flags, + git_pathspec *ps); + +/** + * Free memory associates with a git_pathspec_match_list + * + * @param m The git_pathspec_match_list to be freed + */ +GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m); + +/** + * Get the number of items in a match list. + * + * @param m The git_pathspec_match_list object + * @return Number of items in match list + */ +GIT_EXTERN(size_t) git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m); + +/** + * Get a matching filename by position. + * + * This routine cannot be used if the match list was generated by + * `git_pathspec_match_diff`. If so, it will always return NULL. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the list + * @return The filename of the match + */ +GIT_EXTERN(const char *) git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos); + +/** + * Get a matching diff delta by position. + * + * This routine can only be used if the match list was generated by + * `git_pathspec_match_diff`. Otherwise it will always return NULL. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the list + * @return The filename of the match + */ +GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos); + +/** + * Get the number of pathspec items that did not match. + * + * This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when + * generating the git_pathspec_match_list. + * + * @param m The git_pathspec_match_list object + * @return Number of items in original pathspec that had no matches + */ +GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m); + +/** + * Get an original pathspec string that had no matches. + * + * This will be return NULL for positions out of range. + * + * @param m The git_pathspec_match_list object + * @param pos The index into the failed items + * @return The pathspec pattern that didn't match anything + */ +GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos); + +#endif diff --git a/include/git2/push.h b/include/git2/push.h index f92308144..77ef74039 100644 --- a/include/git2/push.h +++ b/include/git2/push.h @@ -8,6 +8,7 @@ #define INCLUDE_git_push_h__ #include "common.h" +#include "pack.h" /** * @file git2/push.h @@ -38,6 +39,13 @@ typedef struct { #define GIT_PUSH_OPTIONS_VERSION 1 #define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION } +/** Push network progress notification function */ +typedef int (*git_push_transfer_progress)( + unsigned int current, + unsigned int total, + size_t bytes, + void* payload); + /** * Create a new push object * @@ -60,6 +68,27 @@ GIT_EXTERN(int) git_push_set_options( git_push *push, const git_push_options *opts); +/** + * Set the callbacks for a push + * + * @param push The push object + * @param pack_progress_cb Function to call with progress information during + * pack building. Be aware that this is called inline with pack building + * operations, so performance may be affected. + * @param pack_progress_cb_payload Payload for the pack progress callback. + * @param transfer_progress_cb Function to call with progress information during + * the upload portion of a push. Be aware that this is called inline with + * pack building operations, so performance may be affected. + * @param transfer_progress_cb_payload Payload for the network progress callback. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_push_set_callbacks( + git_push *push, + git_packbuilder_progress pack_progress_cb, + void *pack_progress_cb_payload, + git_push_transfer_progress transfer_progress_cb, + void *transfer_progress_cb_payload); + /** * Add a refspec to be pushed * @@ -98,7 +127,7 @@ GIT_EXTERN(int) git_push_finish(git_push *push); * * @param push The push object * - * @return true if equal, false otherwise + * @return true if remote side successfully unpacked, false otherwise */ GIT_EXTERN(int) git_push_unpack_ok(git_push *push); diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 4944530af..2d1b6eeaa 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -31,10 +31,11 @@ GIT_BEGIN_DECL * git_reflog_free(). * * @param out pointer to reflog - * @param ref reference to read the reflog for + * @param repo the repostiory + * @param name reference to look up * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_read(git_reflog **out, const git_reference *ref); +GIT_EXTERN(int) git_reflog_read(git_reflog **out, git_repository *repo, const char *name); /** * Write an existing in-memory reflog object back to disk @@ -59,26 +60,45 @@ GIT_EXTERN(int) git_reflog_write(git_reflog *reflog); GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg); /** - * Rename the reflog for the given reference + * Add a new entry to the named reflog. + * + * This utility function loads the named reflog, appends to it and + * writes it back out to the backend. + * + * `msg` is optional and can be NULL. + * + * @param repo the repository to act on + * @param name the reflog's name + * @param id the OID the reference is now pointing to + * @param committer the signature of the committer + * @param msg the reflog message + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_append_to(git_repository *repo, const char *name, const git_oid *id, const git_signature *committer, const char *msg); + +/** + * Rename a reflog * * The reflog to be renamed is expected to already exist * * The new name will be checked for validity. * See `git_reference_create_symbolic()` for rules about valid names. * - * @param ref the reference - * @param name the new name of the reference + * @param repo the repository + * @param old_name the old name of the reference + * @param new_name the new name of the reference * @return 0 on success, GIT_EINVALIDSPEC or an error code */ -GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *name); +GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name); /** * Delete the reflog for the given reference * - * @param ref the reference + * @param repo the repository + * @param name the reflog to delete * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_delete(git_reference *ref); +GIT_EXTERN(int) git_reflog_delete(git_repository *repo, const char *name); /** * Get the number of log entries in a reflog diff --git a/include/git2/refs.h b/include/git2/refs.h index 795f7ab27..4041947f6 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -32,7 +32,7 @@ GIT_BEGIN_DECL * @param out pointer to the looked-up reference * @param repo the repository to look up the reference * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) - * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. + * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name); @@ -49,7 +49,7 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, * @param out Pointer to oid to be filled in * @param repo The repository in which to look up the reference * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) - * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. + * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_reference_name_to_id( git_oid *out, git_repository *repo, const char *name); @@ -94,7 +94,7 @@ GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, co * @param name The name of the reference * @param target The target of the reference * @param force Overwrite existing references - * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force); @@ -126,7 +126,7 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force); @@ -225,7 +225,7 @@ GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref); * @param out Pointer to the newly created reference * @param ref The reference * @param target The new target for the reference - * @return 0 on success, EINVALIDSPEC or an error code + * @return 0 on success, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reference_symbolic_set_target( git_reference **out, @@ -268,7 +268,7 @@ GIT_EXTERN(int) git_reference_set_target( * @param ref The reference to rename * @param new_name The new name for the reference * @param force Overwrite an existing reference - * @return 0 on success, EINVALIDSPEC, EEXISTS or an error code + * @return 0 on success, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code * */ GIT_EXTERN(int) git_reference_rename( @@ -442,9 +442,18 @@ GIT_EXTERN(int) git_reference_is_branch(git_reference *ref); */ GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); +/** + * Check if a reference is a tag + * + * @param ref A git reference + * + * @return 1 when the reference lives in the refs/tags + * namespace; 0 otherwise. + */ +GIT_EXTERN(int) git_reference_is_tag(git_reference *ref); typedef enum { - GIT_REF_FORMAT_NORMAL = 0, + GIT_REF_FORMAT_NORMAL = 0u, /** * Control whether one-level refnames are accepted @@ -452,7 +461,7 @@ typedef enum { * components). Those are expected to be written only using * uppercase letters and underscore (FETCH_HEAD, ...) */ - GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0), + GIT_REF_FORMAT_ALLOW_ONELEVEL = (1u << 0), /** * Interpret the provided name as a reference pattern for a @@ -461,14 +470,14 @@ typedef enum { * in place of a one full pathname component * (e.g., foo//bar but not foo/bar). */ - GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1), + GIT_REF_FORMAT_REFSPEC_PATTERN = (1u << 1), /** * Interpret the name as part of a refspec in shorthand form * so the `ONELEVEL` naming rules aren't enforced and 'master' * becomes a valid name. */ - GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1 << 2), + GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1u << 2), } git_reference_normalize_t; /** @@ -488,7 +497,7 @@ typedef enum { * @param name Reference name to be checked. * @param flags Flags to constrain name validation rules - see the * GIT_REF_FORMAT constants above. - * @return 0 on success, GIT_EBUFS if buffer is too small, EINVALIDSPEC + * @return 0 on success, GIT_EBUFS if buffer is too small, GIT_EINVALIDSPEC * or an error code. */ GIT_EXTERN(int) git_reference_normalize_name( diff --git a/include/git2/remote.h b/include/git2/remote.h index 45d15d0a3..7410909dc 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -25,13 +25,6 @@ GIT_BEGIN_DECL typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); -/* - * TODO: This functions still need to be implemented: - * - _listcb/_foreach - * - _add - * - _rename - * - _del (needs support from config) - */ /** * Add a remote with the default fetch refspec to the repository's configuration. This @@ -49,6 +42,25 @@ GIT_EXTERN(int) git_remote_create( const char *name, const char *url); +/** + * Add a remote with the provided fetch refspec (or default if NULL) to the repository's + * configuration. This + * calls git_remote_save before returning. + * + * @param out the resulting remote + * @param repo the repository in which to create the remote + * @param name the remote's name + * @param url the remote's url + * @param fetch the remote fetch value + * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code + */ +GIT_EXTERN(int) git_remote_create_with_fetchspec( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + const char *fetch); + /** * Create a remote in memory * @@ -61,7 +73,7 @@ GIT_EXTERN(int) git_remote_create( * * @param out pointer to the new remote object * @param repo the associated repository - * @param fetch the fetch refspec to use for this remote. May be NULL for defaults. + * @param fetch the fetch refspec to use for this remote. * @param url the remote repository's URL * @return 0 or an error code */ @@ -95,6 +107,14 @@ GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const ch */ GIT_EXTERN(int) git_remote_save(const git_remote *remote); +/** + * Get the remote's repository + * + * @param remote the remote + * @return a pointer to the repository + */ +GIT_EXTERN(git_repository *) git_remote_owner(const git_remote *remote); + /** * Get the remote's name * @@ -144,8 +164,11 @@ GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url); /** * Add a fetch refspec to the remote * + * Convenience function for adding a single fetch refspec to the + * current list in the remote. + * * @param remote the remote - * @apram refspec the new fetch refspec + * @param refspec the new fetch refspec * @return 0 or an error value */ GIT_EXTERN(int) git_remote_add_fetch(git_remote *remote, const char *refspec); @@ -161,9 +184,22 @@ GIT_EXTERN(int) git_remote_add_fetch(git_remote *remote, const char *refspec); */ GIT_EXTERN(int) git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote); +/** + * Set the remote's list of fetch refspecs + * + * The contents of the string array are copied. + * + * @param remote the remote to modify + * @param array the new list of fetch resfpecs + */ +GIT_EXTERN(int) git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array); + /** * Add a push refspec to the remote * + * Convenience function for adding a single push refspec to the + * current list in the remote. + * * @param remote the remote * @param refspec the new push refspec * @return 0 or an error value @@ -181,6 +217,16 @@ GIT_EXTERN(int) git_remote_add_push(git_remote *remote, const char *refspec); */ GIT_EXTERN(int) git_remote_get_push_refspecs(git_strarray *array, git_remote *remote); +/** + * Set the remote's list of push refspecs + * + * The contents of the string array are copied. + * + * @param remote the remote to modify + * @param array the new list of push resfpecs + */ +GIT_EXTERN(int) git_remote_set_push_refspecs(git_remote *remote, git_strarray *array); + /** * Clear the refspecs * @@ -207,15 +253,6 @@ GIT_EXTERN(size_t) git_remote_refspec_count(git_remote *remote); */ GIT_EXTERN(const git_refspec *)git_remote_get_refspec(git_remote *remote, size_t n); -/** - * Remove a refspec from the remote - * - * @param remote the remote to query - * @param n the refspec to remove - * @return 0 or GIT_ENOTFOUND - */ -GIT_EXTERN(int) git_remote_remove_refspec(git_remote *remote, size_t n); - /** * Open a connection to a remote * @@ -236,36 +273,30 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction); * The remote (or more exactly its transport) must be connected. The * memory belongs to the remote. * - * If you a return a non-zero value from the callback, this will stop - * looping over the refs. + * The array will stay valid as long as the remote object exists and + * its transport isn't changed, but a copy is recommended for usage of + * the data. * + * @param out pointer to the array + * @param size the number of remote heads * @param remote the remote - * @param list_cb function to call with each ref discovered at the remote - * @param payload additional data to pass to the callback - * @return 0 on success, GIT_EUSER on non-zero callback, or error code + * @return 0 on success, or an error code */ -GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); +GIT_EXTERN(int) git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote); /** - * Download the packfile + * Download and index the packfile * - * Negotiate what objects should be downloaded and download the - * packfile with those objects. The packfile is downloaded with a - * temporary filename, as it's final name is not known yet. If there - * was no packfile needed (all the objects were available locally), - * filename will be NULL and the function will return success. + * Connect to the remote if it hasn't been done yet, negotiate with + * the remote git which objects are missing, download and index the + * packfile. + * + * The .idx file will be created and both it and the packfile with be + * renamed to their final name. * - * @param remote the remote to download from - * @param progress_cb function to call with progress information. Be aware that - * this is called inline with network and indexing operations, so performance - * may be affected. - * @param payload payload for the progress callback * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_download( - git_remote *remote, - git_transfer_progress_callback progress_cb, - void *payload); +GIT_EXTERN(int) git_remote_download(git_remote *remote); /** * Check whether the remote is connected @@ -316,6 +347,17 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote); */ GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); +/** + * Download new data and update tips + * + * Convenience function to connect to a remote, download the data, + * disconnect and update the remote-tracking branches. + * + * @param remote the remote to fetch from + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_fetch(git_remote *remote); + /** * Return whether a string is a valid remote URL * @@ -351,21 +393,6 @@ GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo); */ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); -/** - * Set a credentials acquisition callback for this remote. If the remote is - * not available for anonymous access, then you must set this callback in order - * to provide credentials to the transport at the time of authentication - * failure so that retry can be performed. - * - * @param remote the remote to configure - * @param cred_acquire_cb The credentials acquisition callback to use (defaults - * to NULL) - */ -GIT_EXTERN(void) git_remote_set_cred_acquire_cb( - git_remote *remote, - git_cred_acquire_cb cred_acquire_cb, - void *payload); - /** * Sets a custom transport for the remote. The caller can use this function * to bypass the automatic discovery of a transport by URL scheme (i.e. @@ -395,13 +422,47 @@ typedef enum git_remote_completion_type { /** * The callback settings structure * - * Set the calbacks to be called by the remote. + * Set the callbacks to be called by the remote when informing the user + * about the progress of the network operations. */ struct git_remote_callbacks { unsigned int version; - void (*progress)(const char *str, int len, void *data); + /** + * Textual progress from the remote. Text send over the + * progress side-band will be passed to this function (this is + * the 'counting objects' output. + */ + int (*progress)(const char *str, int len, void *data); + + /** + * Completion is called when different parts of the download + * process are done (currently unused). + */ int (*completion)(git_remote_completion_type type, void *data); + + /** + * This will be called if the remote host requires + * authentication in order to connect to it. + */ + int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data); + + /** + * During the download of new data, this will be regularly + * called with the current count of progress done by the + * indexer. + */ + int (*transfer_progress)(const git_transfer_progress *stats, void *data); + + /** + * Each time a reference is updated locally, this function + * will be called with information about it. + */ int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + + /** + * This will be passed to each of the callbacks in this struct + * as the last parameter. + */ void *payload; }; @@ -418,7 +479,7 @@ struct git_remote_callbacks { * @param callbacks a pointer to the user's callback settings * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks); +GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks); /** * Get the statistics structure that is filled in by the fetch operation. diff --git a/include/git2/repository.h b/include/git2/repository.h index 2164cfac1..b4d561992 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -94,10 +94,14 @@ GIT_EXTERN(int) git_repository_discover( * changes from the `stat` system call). (E.g. Searching in a user's home * directory "/home/user/source/" will not return "/.git/" as the found * repo if "/" is a different filesystem than "/home".) + * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless + * of core.bare config, and defer loading config file for faster setup. + * Unlike `git_repository_open_bare`, this can follow gitlinks. */ typedef enum { GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), + GIT_REPOSITORY_OPEN_BARE = (1 << 2), } git_repository_open_flag_t; /** @@ -178,7 +182,7 @@ GIT_EXTERN(int) git_repository_init( * when initializing a new repo. Details of individual values are: * * * BARE - Create a bare repository with no working directory. - * * NO_REINIT - Return an EEXISTS error if the repo_path appears to + * * NO_REINIT - Return an GIT_EEXISTS error if the repo_path appears to * already be an git repository. * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo * path for non-bare repos (if it is not already there), but @@ -293,7 +297,7 @@ GIT_EXTERN(int) git_repository_init_ext( * @param out pointer to the reference which will be retrieved * @param repo a repository object * - * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing + * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise */ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); @@ -311,16 +315,16 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); GIT_EXTERN(int) git_repository_head_detached(git_repository *repo); /** - * Check if the current branch is an orphan + * Check if the current branch is unborn * - * An orphan branch is one named from HEAD but which doesn't exist in + * An unborn branch is one named from HEAD but which doesn't exist in * the refs namespace, because it doesn't have any commit to point to. * * @param repo Repo to test - * @return 1 if the current branch is an orphan, 0 if it's not; error + * @return 1 if the current branch is unborn, 0 if it's not; error * code if there was an error */ -GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo); +GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo); /** * Check if a repository is empty @@ -471,7 +475,7 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); * @param out Buffer to write data into or NULL to just read required size * @param len Length of `out` buffer in bytes * @param repo Repository to read prepared message from - * @return GIT_ENOUTFOUND if no message exists, other value < 0 for other + * @return GIT_ENOTFOUND if no message exists, other value < 0 for other * errors, or total bytes in message (may be > `len`) on success */ GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo); @@ -601,13 +605,13 @@ GIT_EXTERN(int) git_repository_set_head_detached( * If the HEAD is already detached and points to a Tag, the HEAD is * updated into making it point to the peeled Commit, and 0 is returned. * - * If the HEAD is already detached and points to a non commitish, the HEAD is + * If the HEAD is already detached and points to a non commitish, the HEAD is * unaltered, and -1 is returned. * * Otherwise, the HEAD will be detached and point to the peeled Commit. * * @param repo Repository pointer - * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing + * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing * branch or an error code */ GIT_EXTERN(int) git_repository_detach_head( diff --git a/include/git2/revparse.h b/include/git2/revparse.h index 786a9da57..d170e1621 100644 --- a/include/git2/revparse.h +++ b/include/git2/revparse.h @@ -10,7 +10,6 @@ #include "common.h" #include "types.h" - /** * @file git2/revparse.h * @brief Git revision parsing routines @@ -21,27 +20,37 @@ GIT_BEGIN_DECL /** - * Find a single object, as specified by a revision string. See `man gitrevisions`, - * or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * Find a single object, as specified by a revision string. + * + * See `man gitrevisions`, or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * information on the syntax accepted. * + * The returned object should be released with `git_object_free` when no + * longer needed. + * * @param out pointer to output object * @param repo the repository to search in * @param spec the textual specification for an object * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code */ -GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec); +GIT_EXTERN(int) git_revparse_single( + git_object **out, git_repository *repo, const char *spec); /** - * Find a single object, as specified by a revision string. - * See `man gitrevisions`, - * or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * Find a single object and intermediate reference by a revision string. + * + * See `man gitrevisions`, or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for * information on the syntax accepted. * * In some cases (`@{<-n>}` or `@{upstream}`), the expression may * point to an intermediate reference. When such expressions are being passed * in, `reference_out` will be valued as well. * + * The returned object should be released with `git_object_free` and the + * returned reference with `git_reference_free` when no longer needed. + * * @param object_out pointer to output object * @param reference_out pointer to output reference or NULL * @param repo the repository to search in @@ -76,25 +85,27 @@ typedef struct { git_object *from; /** The right element of the revspec; must be freed by the user */ git_object *to; - /** The intent of the revspec */ + /** The intent of the revspec (i.e. `git_revparse_mode_t` flags) */ unsigned int flags; } git_revspec; /** - * Parse a revision string for `from`, `to`, and intent. See `man gitrevisions` or - * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for information - * on the syntax accepted. + * Parse a revision string for `from`, `to`, and intent. * - * @param revspec Pointer to an user-allocated git_revspec struct where the result - * of the rev-parse will be stored + * See `man gitrevisions` or + * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for + * information on the syntax accepted. + * + * @param revspec Pointer to an user-allocated git_revspec struct where + * the result of the rev-parse will be stored * @param repo the repository to search in * @param spec the rev-parse spec to parse * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code */ GIT_EXTERN(int) git_revparse( - git_revspec *revspec, - git_repository *repo, - const char *spec); + git_revspec *revspec, + git_repository *repo, + const char *spec); /** @} */ diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 8bfe0b502..c59b79938 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -231,6 +231,14 @@ GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode); */ GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range); +/** + * Simplify the history by first-parent + * + * No parents other than the first for each commit will be enqueued. + */ +GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk); + + /** * Free a revision walker previously allocated. * diff --git a/include/git2/signature.h b/include/git2/signature.h index 00d19de66..2fa46d032 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -48,6 +48,19 @@ GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const c */ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); +/** + * Create a new action signature with default user and now timestamp. + * + * This looks up the user.name and user.email from the configuration and + * uses the current time as the timestamp, and creates a new signature + * based on that information. It will return GIT_ENOTFOUND if either the + * user.name or user.email are not set. + * + * @param out new signature + * @param repo repository pointer + * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code + */ +GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo); /** * Create a copy of an existing signature. All internal strings are also diff --git a/include/git2/stash.h b/include/git2/stash.h index 68d1b5413..b48d33f5d 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -57,7 +57,7 @@ typedef enum { GIT_EXTERN(int) git_stash_save( git_oid *out, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message, unsigned int flags); diff --git a/include/git2/status.h b/include/git2/status.h index 63aea2f3b..4ec3432df 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -60,25 +60,24 @@ typedef int (*git_status_cb)( const char *path, unsigned int status_flags, void *payload); /** - * For extended status, select the files on which to report status. + * Select the files on which to report status. + * + * With `git_status_foreach_ext`, this will control which changes get + * callbacks. With `git_status_list_new`, these will control which + * changes are included in the list. * * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly - * matches `git status --porcelain` where each file gets a callback - * indicating its status in the index and in the working directory. + * matches `git status --porcelain` regarding which files are + * included and in what order. * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index * comparison, not looking at working directory changes. * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to * working directory comparison, not comparing the index to the HEAD. - * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only, - * issuing (up to) two callbacks per file (first index, then workdir). - * This is slightly more efficient than separate calls and can make it - * easier to emulate plain `git status` text output. */ typedef enum { GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, GIT_STATUS_SHOW_INDEX_ONLY = 1, GIT_STATUS_SHOW_WORKDIR_ONLY = 2, - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR = 3, } git_status_show_t; /** @@ -108,7 +107,7 @@ typedef enum { * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection * should be processed between the head and the index and enables * the GIT_STATUS_INDEX_RENAMED as a possible status flag. - * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename * detection should be run between the index and the working directory * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case @@ -117,6 +116,11 @@ typedef enum { * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case * sensitivity for the file system and forces the output to be in * case-insensitive order + * - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection + * should include rewritten files + * - GIT_STATUS_OPT_NO_REFRESH bypasses the default status behavior of + * doing a "soft" index reload (i.e. reloading the index data if the + * file on disk has been modified outside libgit2). * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -135,6 +139,8 @@ typedef enum { GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), + GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11), + GIT_STATUS_OPT_NO_REFRESH = (1u << 12), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ @@ -235,12 +241,12 @@ GIT_EXTERN(int) git_status_foreach_ext( * This is not quite the same as calling `git_status_foreach_ext()` with * the pathspec set to the specified path. * - * @param status_flags The status value for the file + * @param status_flags Output combination of git_status_t values for file * @param repo A repository object - * @param path The file to retrieve status for, rooted at the repo's workdir + * @param path The file to retrieve status for relative to the repo workdir * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, - * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, - * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. + * index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files + * or if it refers to a folder, and -1 on other errors. */ GIT_EXTERN(int) git_status_file( unsigned int *status_flags, diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 91b5300ae..186f263f5 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -14,51 +14,18 @@ /** * @file git2/submodule.h * @brief Git submodule management utilities - * @defgroup git_submodule Git submodule management routines - * @ingroup Git - * @{ - */ -GIT_BEGIN_DECL - -/** - * Opaque structure representing a submodule. * * Submodule support in libgit2 builds a list of known submodules and keeps * it in the repository. The list is built from the .gitmodules file, the * .git/config file, the index, and the HEAD tree. Items in the working * directory that look like submodules (i.e. a git repo) but are not * mentioned in those places won't be tracked. - */ -typedef struct git_submodule git_submodule; - -/** - * Values that could be specified for the update rule of a submodule. * - * Use the DEFAULT value if you have altered the update value via - * `git_submodule_set_update()` and wish to reset to the original default. + * @defgroup git_submodule Git submodule management routines + * @ingroup Git + * @{ */ -typedef enum { - GIT_SUBMODULE_UPDATE_DEFAULT = -1, - GIT_SUBMODULE_UPDATE_CHECKOUT = 0, - GIT_SUBMODULE_UPDATE_REBASE = 1, - GIT_SUBMODULE_UPDATE_MERGE = 2, - GIT_SUBMODULE_UPDATE_NONE = 3 -} git_submodule_update_t; - -/** - * Values that could be specified for how closely to examine the - * working directory when getting submodule status. - * - * Use the DEFUALT value if you have altered the ignore value via - * `git_submodule_set_ignore()` and wish to reset to the original value. - */ -typedef enum { - GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */ - GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */ -} git_submodule_ignore_t; +GIT_BEGIN_DECL /** * Return codes for submodule status. @@ -119,19 +86,9 @@ typedef enum { GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13), } git_submodule_status_t; -#define GIT_SUBMODULE_STATUS__IN_FLAGS \ - (GIT_SUBMODULE_STATUS_IN_HEAD | \ - GIT_SUBMODULE_STATUS_IN_INDEX | \ - GIT_SUBMODULE_STATUS_IN_CONFIG | \ - GIT_SUBMODULE_STATUS_IN_WD) - -#define GIT_SUBMODULE_STATUS__INDEX_FLAGS \ - (GIT_SUBMODULE_STATUS_INDEX_ADDED | \ - GIT_SUBMODULE_STATUS_INDEX_DELETED | \ - GIT_SUBMODULE_STATUS_INDEX_MODIFIED) - -#define GIT_SUBMODULE_STATUS__WD_FLAGS \ - ~(GIT_SUBMODULE_STATUS__IN_FLAGS | GIT_SUBMODULE_STATUS__INDEX_FLAGS) +#define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu +#define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u +#define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u #define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0) @@ -359,9 +316,10 @@ GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule); GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule); /** - * Get the ignore rule for the submodule. + * Get the ignore rule that will be used for the submodule. * - * There are four ignore values: + * These values control the behavior of `git_submodule_status()` for this + * submodule. There are four ignore values: * * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents * of the submodule from a clean checkout to be dirty, including the @@ -375,6 +333,13 @@ GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule); * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo. * The working directory will be consider clean so long as there is a * checked out version present. + * + * plus the special **GIT_SUBMODULE_IGNORE_RESET** which can be used with + * `git_submodule_set_ignore()` to revert to the on-disk setting. + * + * @param submodule The submodule to check + * @return The current git_submodule_ignore_t valyue what will be used for + * this submodule. */ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( git_submodule *submodule); @@ -382,15 +347,17 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( /** * Set the ignore rule for the submodule. * - * This sets the ignore rule in memory for the submodule. This will be used - * for any following actions (such as `git_submodule_status()`) while the - * submodule is in memory. You should call `git_submodule_save()` if you - * want to persist the new ignore role. + * This sets the in-memory ignore rule for the submodule which will + * control the behavior of `git_submodule_status()`. * - * Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling - * `git_submodule_reload()` will revert the rule to the value that was in the - * original config. + * To make changes persistent, call `git_submodule_save()` to write the + * value to disk (in the ".gitmodules" and ".git/config" files). * + * Call with `GIT_SUBMODULE_IGNORE_RESET` or call `git_submodule_reload()` + * to revert the in-memory rule to the value that is on disk. + * + * @param submodule The submodule to update + * @param ignore The new value for the ignore rule * @return old value for ignore */ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( @@ -398,7 +365,16 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( git_submodule_ignore_t ignore); /** - * Get the update rule for the submodule. + * Get the update rule that will be used for the submodule. + * + * This value controls the behavior of the `git submodule update` command. + * There are four useful values documented with `git_submodule_update_t` + * plus the `GIT_SUBMODULE_UPDATE_RESET` which can be used to revert to + * the on-disk setting. + * + * @param submodule The submodule to check + * @return The current git_submodule_update_t value that will be used + * for this submodule. */ GIT_EXTERN(git_submodule_update_t) git_submodule_update( git_submodule *submodule); @@ -406,13 +382,17 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update( /** * Set the update rule for the submodule. * - * This sets the update rule in memory for the submodule. You should call - * `git_submodule_save()` if you want to persist the new update rule. + * The initial value comes from the ".git/config" setting of + * `submodule.$name.update` for this submodule (which is initialized from + * the ".gitmodules" file). Using this function sets the update rule in + * memory for the submodule. Call `git_submodule_save()` to write out the + * new update rule. * - * Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling - * `git_submodule_reload()` will revert the rule to the value that was in the - * original config. + * Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling + * `git_submodule_reload()` will revert the rule to the on disk value. * + * @param submodule The submodule to update + * @param update The new value to use * @return old value for update */ GIT_EXTERN(git_submodule_update_t) git_submodule_set_update( diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 11e59cf03..419ad7ea7 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -20,6 +20,33 @@ */ GIT_BEGIN_DECL +/** + * Every iterator must have this struct as its first element, so the + * API can talk to it. You'd define your iterator as + * + * struct my_iterator { + * git_config_iterator parent; + * ... + * } + * + * and assign `iter->parent.backend` to your `git_config_backend`. + */ +struct git_config_iterator { + git_config_backend *backend; + unsigned int flags; + + /** + * Return the current entry and advance the iterator. The + * memory belongs to the library. + */ + int (*next)(git_config_entry **entry, git_config_iterator *iter); + + /** + * Free the iterator + */ + void (*free)(git_config_iterator *iter); +}; + /** * Generic backend that implements the interface to * access a configuration file @@ -31,11 +58,11 @@ struct git_config_backend { /* Open means open the file/database and parse if necessary */ int (*open)(struct git_config_backend *, git_config_level_t level); int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry); - int (*get_multivar)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload); int (*set)(struct git_config_backend *, const char *key, const char *value); int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_backend *, const char *key); - int (*foreach)(struct git_config_backend *, const char *, git_config_foreach_cb callback, void *payload); + int (*del_multivar)(struct git_config_backend *, const char *key, const char *regexp); + int (*iterator)(git_config_iterator **, struct git_config_backend *); int (*refresh)(struct git_config_backend *); void (*free)(struct git_config_backend *); }; diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h new file mode 100644 index 000000000..94ad3aed4 --- /dev/null +++ b/include/git2/sys/filter.h @@ -0,0 +1,292 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_filter_h__ +#define INCLUDE_sys_git_filter_h__ + +#include "git2/filter.h" + +/** + * @file git2/sys/filter.h + * @brief Git filter backend and plugin routines + * @defgroup git_backend Git custom backend APIs + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Look up a filter by name + * + * @param name The name of the filter + * @return Pointer to the filter object or NULL if not found + */ +GIT_EXTERN(git_filter *) git_filter_lookup(const char *name); + +#define GIT_FILTER_CRLF "crlf" +#define GIT_FILTER_IDENT "ident" + +/** + * This is priority that the internal CRLF filter will be registered with + */ +#define GIT_FILTER_CRLF_PRIORITY 0 + +/** + * This is priority that the internal ident filter will be registered with + */ +#define GIT_FILTER_IDENT_PRIORITY 100 + +/** + * This is priority to use with a custom filter to imitate a core Git + * filter driver, so that it will be run last on checkout and first on + * checkin. You do not have to use this, but it helps compatibility. + */ +#define GIT_FILTER_DRIVER_PRIORITY 200 + +/** + * Create a new empty filter list + * + * Normally you won't use this because `git_filter_list_load` will create + * the filter list for you, but you can use this in combination with the + * `git_filter_lookup` and `git_filter_list_push` functions to assemble + * your own chains of filters. + */ +GIT_EXTERN(int) git_filter_list_new( + git_filter_list **out, git_repository *repo, git_filter_mode_t mode); + +/** + * Add a filter to a filter list with the given payload. + * + * Normally you won't have to do this because the filter list is created + * by calling the "check" function on registered filters when the filter + * attributes are set, but this does allow more direct manipulation of + * filter lists when desired. + * + * Note that normally the "check" function can set up a payload for the + * filter. Using this function, you can either pass in a payload if you + * know the expected payload format, or you can pass NULL. Some filters + * may fail with a NULL payload. Good luck! + */ +GIT_EXTERN(int) git_filter_list_push( + git_filter_list *fl, git_filter *filter, void *payload); + +/** + * Look up how many filters are in the list + * + * We will attempt to apply all of these filters to any data passed in, + * but note that the filter apply action still has the option of skipping + * data that is passed in (for example, the CRLF filter will skip data + * that appears to be binary). + * + * @param fl A filter list + * @return The number of filters in the list + */ +GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl); + +/** + * A filter source represents a file/blob to be processed + */ +typedef struct git_filter_source git_filter_source; + +/** + * Get the repository that the source data is coming from. + */ +GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src); + +/** + * Get the path that the source data is coming from. + */ +GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src); + +/** + * Get the file mode of the source file + * If the mode is unknown, this will return 0 + */ +GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src); + +/** + * Get the OID of the source + * If the OID is unknown (often the case with GIT_FILTER_CLEAN) then + * this will return NULL. + */ +GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src); + +/** + * Get the git_filter_mode_t to be applied + */ +GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(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 + * + * Specified as `filter.initialize`, this is an optional callback invoked + * before a filter is first used. It will be called once at most. + * + * If non-NULL, the filter's `initialize` callback will be invoked right + * before the first use of the filter, so you can defer expensive + * initialization operations (in case libgit2 is being used in a way that + * doesn't need the filter). + */ +typedef int (*git_filter_init_fn)(git_filter *self); + +/** + * Shutdown callback on filter + * + * Specified as `filter.shutdown`, this is an optional callback invoked + * when the filter is unregistered or when libgit2 is shutting down. It + * will be called once at most and should release resources as needed. + * + * Typically this function will free the `git_filter` object itself. + */ +typedef void (*git_filter_shutdown_fn)(git_filter *self); + +/** + * Callback to decide if a given source needs this filter + * + * Specified as `filter.check`, this is an optional callback that checks + * if filtering is needed for a given source. + * + * It should return 0 if the filter should be applied (i.e. success), + * GIT_PASSTHROUGH if the filter should not be applied, or an error code + * to fail out of the filter processing pipeline and return to the caller. + * + * The `attr_values` will be set to the values of any attributes given in + * the filter definition. See `git_filter` below for more detail. + * + * The `payload` will be a pointer to a reference payload for the filter. + * This will start as NULL, but `check` can assign to this pointer for + * later use by the `apply` callback. Note that the value should be heap + * allocated (not stack), so that it doesn't go away before the `apply` + * callback can use it. If a filter allocates and assigns a value to the + * `payload`, it will need a `cleanup` callback to free the payload. + */ +typedef int (*git_filter_check_fn)( + git_filter *self, + void **payload, /* points to NULL ptr on entry, may be set */ + const git_filter_source *src, + const char **attr_values); + +/** + * Callback to actually perform the data filtering + * + * Specified as `filter.apply`, this is the callback that actually filters + * data. If it successfully writes the output, it should return 0. Like + * `check`, it can return GIT_PASSTHROUGH to indicate that the filter + * doesn't want to run. Other error codes will stop filter processing and + * return to the caller. + * + * The `payload` value will refer to any payload that was set by the + * `check` callback. It may be read from or written to as needed. + */ +typedef int (*git_filter_apply_fn)( + git_filter *self, + void **payload, /* may be read and/or set */ + git_buf *to, + const git_buf *from, + const git_filter_source *src); + +/** + * Callback to clean up after filtering has been applied + * + * Specified as `filter.cleanup`, this is an optional callback invoked + * after the filter has been applied. If the `check` or `apply` callbacks + * allocated a `payload` to keep per-source filter state, use this + * callback to free that payload and release resources as required. + */ +typedef void (*git_filter_cleanup_fn)( + git_filter *self, + void *payload); + +/** + * Filter structure used to register custom filters. + * + * 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 { + unsigned int version; + + const char *attributes; + + git_filter_init_fn initialize; + git_filter_shutdown_fn shutdown; + git_filter_check_fn check; + git_filter_apply_fn apply; + git_filter_cleanup_fn cleanup; +}; + +#define GIT_FILTER_VERSION 1 + +/** + * Register a filter under a given name with a given priority. + * + * As mentioned elsewhere, the initialize callback will not be invoked + * immediately. It is deferred until the filter is used in some way. + * + * A filter's attribute checks and `check` and `apply` callbacks will be + * issued in order of `priority` on smudge (to workdir), and in reverse + * order of `priority` on clean (to odb). + * + * Two filters are preregistered with libgit2: + * - GIT_FILTER_CRLF with priority 0 + * - GIT_FILTER_IDENT with priority 100 + * + * Currently the filter registry is not thread safe, so any registering or + * deregistering of filters must be done outside of any possible usage of + * the filters (i.e. during application setup or shutdown). + * + * @param name A name by which the filter can be referenced. Attempting + * to register with an in-use name will return GIT_EEXISTS. + * @param filter The filter definition. This pointer will be stored as is + * by libgit2 so it must be a durable allocation (either static + * or on the heap). + * @param priority The priority for filter application + * @return 0 on successful registry, error code <0 on failure + */ +GIT_EXTERN(int) git_filter_register( + const char *name, git_filter *filter, int priority); + +/** + * Remove the filter with the given name + * + * Attempting to remove the builtin libgit2 filters is not permitted and + * will return an error. + * + * Currently the filter registry is not thread safe, so any registering or + * deregistering of filters must be done outside of any possible usage of + * the filters (i.e. during application setup or shutdown). + * + * @param name The name under which the filter was registered + * @return 0 on success, error code <0 on failure + */ +GIT_EXTERN(int) git_filter_unregister(const char *name); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h index a32e07036..1a06a4df1 100644 --- a/include/git2/sys/index.h +++ b/include/git2/sys/index.h @@ -72,7 +72,6 @@ GIT_EXTERN(int) git_index_name_add(git_index *index, * Remove all filename conflict entries. * * @param index an existing index object - * @return 0 or an error code */ GIT_EXTERN(void) git_index_name_clear(git_index *index); @@ -168,7 +167,6 @@ GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n); * Remove all resolve undo entries from the index * * @param index an existing index object - * @return 0 or an error code */ GIT_EXTERN(void) git_index_reuc_clear(git_index *index); diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 3cd2734c0..8039a5b82 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -48,12 +48,12 @@ struct git_odb_backend { int (* read_header)( size_t *, git_otype *, git_odb_backend *, const git_oid *); - /* The writer may assume that the object - * has already been hashed and is passed - * in the first parameter. + /** + * Write an object into the backend. The id of the object has + * already been calculated and is passed in. */ int (* write)( - git_oid *, git_odb_backend *, const void *, size_t, git_otype); + git_odb_backend *, const git_oid *, const void *, size_t, git_otype); int (* writestream)( git_odb_stream **, git_odb_backend *, size_t, git_otype); @@ -64,13 +64,23 @@ struct git_odb_backend { int (* exists)( git_odb_backend *, const git_oid *); + /** + * If the backend implements a refreshing mechanism, it should be exposed + * through this endpoint. Each call to `git_odb_refresh()` will invoke it. + * + * However, the backend implementation should try to stay up-to-date as much + * as possible by itself as libgit2 will not automatically invoke + * `git_odb_refresh()`. For instance, a potential strategy for the backend + * implementation to achieve this could be to internally invoke this + * endpoint on failed lookups (ie. `exists()`, `read()`, `read_header()`). + */ int (* refresh)(git_odb_backend *); int (* foreach)( git_odb_backend *, git_odb_foreach_cb cb, void *payload); int (* writepack)( - git_odb_writepack **, git_odb_backend *, + git_odb_writepack **, git_odb_backend *, git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_payload); void (* free)(git_odb_backend *); diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 9b457b074..9cf5073fb 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -103,7 +103,7 @@ struct git_refdb_backend { * Deletes the given reference from the refdb. A refdb implementation * must provide this function. */ - int (*delete)(git_refdb_backend *backend, const char *ref_name); + int (*del)(git_refdb_backend *backend, const char *ref_name); /** * Suggests that the given refdb compress or optimize its references. @@ -119,10 +119,30 @@ struct git_refdb_backend { * provide this function; if it is not provided, nothing will be done. */ void (*free)(git_refdb_backend *backend); + + /** + * Read the reflog for the given reference name. + */ + int (*reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name); + + /** + * Write a reflog to disk. + */ + int (*reflog_write)(git_refdb_backend *backend, git_reflog *reflog); + + /** + * Rename a reflog + */ + int (*reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name); + + /** + * Remove a reflog. + */ + int (*reflog_delete)(git_refdb_backend *backend, const char *name); }; -#define GIT_ODB_BACKEND_VERSION 1 -#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION} +#define GIT_REFDB_BACKEND_VERSION 1 +#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION} /** * Constructors for default filesystem-based refdb backend diff --git a/include/git2/sys/reflog.h b/include/git2/sys/reflog.h new file mode 100644 index 000000000..c9d0041b9 --- /dev/null +++ b/include/git2/sys/reflog.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_reflog_h__ +#define INCLUDE_sys_git_reflog_h__ + +#include "git2/common.h" +#include "git2/types.h" +#include "git2/oid.h" + +GIT_BEGIN_DECL + +GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void); +GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry); + +GIT_END_DECL + +#endif diff --git a/include/git2/sys/refs.h b/include/git2/sys/refs.h index 85963258c..dd95ca12c 100644 --- a/include/git2/sys/refs.h +++ b/include/git2/sys/refs.h @@ -16,7 +16,7 @@ * * @param name the reference name * @param oid the object id for a direct reference - * @param symbolic the target for a symbolic reference + * @param peel the first non-tag object's OID, or NULL * @return the created git_reference or NULL on error */ GIT_EXTERN(git_reference *) git_reference__alloc( @@ -28,7 +28,7 @@ GIT_EXTERN(git_reference *) git_reference__alloc( * Create a new symbolic reference. * * @param name the reference name - * @param symbolic the target for a symbolic reference + * @param target the target for a symbolic reference * @return the created git_reference or NULL on error */ GIT_EXTERN(git_reference *) git_reference__alloc_symbolic( diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h index ba3d65ae5..36f8b5836 100644 --- a/include/git2/sys/repository.h +++ b/include/git2/sys/repository.h @@ -27,7 +27,6 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_repository_new(git_repository **out); - /** * Reset all the internal state in a repository. * @@ -41,6 +40,25 @@ GIT_EXTERN(int) git_repository_new(git_repository **out); */ GIT_EXTERN(void) git_repository__cleanup(git_repository *repo); +/** + * Update the filesystem config settings for an open repository + * + * When a repository is initialized, config values are set based on the + * properties of the filesystem that the repository is on, such as + * "core.ignorecase", "core.filemode", "core.symlinks", etc. If the + * repository is moved to a new filesystem, these properties may no + * longer be correct and API calls may not behave as expected. This + * call reruns the phase of repository initialization that sets those + * properties to compensate for the current filesystem of the repo. + * + * @param repo A repository object + * @param recurse_submodules Should submodules be updated recursively + * @returrn 0 on success, < 0 on error + */ +GIT_EXTERN(int) git_repository_reinit_filesystem( + git_repository *repo, + int recurse_submodules); + /** * Set the configuration file for this repository * diff --git a/include/git2/transport.h b/include/git2/transport.h index 81bb3abe1..caabd0465 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -28,22 +28,31 @@ GIT_BEGIN_DECL *** Begin interface for credentials acquisition *** */ +/** Authentication type requested */ typedef enum { /* git_cred_userpass_plaintext */ - GIT_CREDTYPE_USERPASS_PLAINTEXT = 1, - GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE = 2, - GIT_CREDTYPE_SSH_PUBLICKEY = 3, + GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0), + + /* git_cred_ssh_key */ + GIT_CREDTYPE_SSH_KEY = (1u << 1), + + /* git_cred_ssh_custom */ + GIT_CREDTYPE_SSH_CUSTOM = (1u << 2), + + /* git_cred_default */ + GIT_CREDTYPE_DEFAULT = (1u << 3), } git_credtype_t; /* The base structure for all credential types */ -typedef struct git_cred { - git_credtype_t credtype; - void (*free)( - struct git_cred *cred); -} git_cred; +typedef struct git_cred git_cred; -/* A plaintext username and password */ -typedef struct git_cred_userpass_plaintext { +struct git_cred { + git_credtype_t credtype; + void (*free)(git_cred *cred); +}; + +/** A plaintext username and password */ +typedef struct { git_cred parent; char *username; char *password; @@ -51,27 +60,46 @@ typedef struct git_cred_userpass_plaintext { #ifdef GIT_SSH typedef LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*git_cred_sign_callback)); - -/* A ssh key file and passphrase */ -typedef struct git_cred_ssh_keyfile_passphrase { - git_cred parent; - char *publickey; - char *privatekey; - char *passphrase; -} git_cred_ssh_keyfile_passphrase; - -/* A ssh public key and authentication callback */ -typedef struct git_cred_ssh_publickey { - git_cred parent; - char *publickey; - size_t publickey_len; - void *sign_callback; - void *sign_data; -} git_cred_ssh_publickey; +#else +typedef int (*git_cred_sign_callback)(void *, ...); #endif /** - * Creates a new plain-text username and password credential object. + * A ssh key from disk + */ +typedef struct git_cred_ssh_key { + git_cred parent; + char *username; + char *publickey; + char *privatekey; + char *passphrase; +} git_cred_ssh_key; + +/** + * A key with a custom signature function + */ +typedef struct git_cred_ssh_custom { + git_cred parent; + char *username; + char *publickey; + size_t publickey_len; + void *sign_callback; + void *sign_data; +} git_cred_ssh_custom; + +/** A key for NTLM/Kerberos "default" credentials */ +typedef struct git_cred git_cred_default; + +/** + * Check whether a credential object contains username information. + * + * @param cred object to check + * @return 1 if the credential object has non-NULL username, 0 otherwise + */ +GIT_EXTERN(int) git_cred_has_username(git_cred *cred); + +/** + * Create a new plain-text username and password credential object. * The supplied credential parameter will be internally duplicated. * * @param out The newly created credential object. @@ -84,52 +112,68 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new( const char *username, const char *password); -#ifdef GIT_SSH /** - * Creates a new ssh key file and passphrase credential object. + * Create a new passphrase-protected ssh key credential object. * The supplied credential parameter will be internally duplicated. * * @param out The newly created credential object. + * @param username username to use to authenticate * @param publickey The path to the public key of the credential. * @param privatekey The path to the private key of the credential. * @param passphrase The passphrase of the credential. * @return 0 for success or an error code for failure */ -GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( +GIT_EXTERN(int) git_cred_ssh_key_new( git_cred **out, + const char *username, const char *publickey, const char *privatekey, - const char *passphrase); + const char *passphrase); /** - * Creates a new ssh public key credential object. + * Create an ssh key credential with a custom signing function. + * + * This lets you use your own function to sign the challenge. + * + * This function and its credential type is provided for completeness + * and wraps `libssh2_userauth_publickey()`, which is undocumented. + * * The supplied credential parameter will be internally duplicated. * * @param out The newly created credential object. + * @param username username to use to authenticate * @param publickey The bytes of the public key. * @param publickey_len The length of the public key in bytes. - * @param sign_callback The callback method for authenticating. - * @param sign_data The abstract data sent to the sign_callback method. + * @param sign_fn The callback method to sign the data during the challenge. + * @param sign_data The data to pass to the sign function. * @return 0 for success or an error code for failure */ -GIT_EXTERN(int) git_cred_ssh_publickey_new( +GIT_EXTERN(int) git_cred_ssh_custom_new( git_cred **out, + const char *username, const char *publickey, - size_t publickey_len, - git_cred_sign_callback, - void *sign_data); -#endif + size_t publickey_len, + git_cred_sign_callback sign_fn, + void *sign_data); + +/** + * Create a "default" credential usable for Negotiate mechanisms like NTLM + * or Kerberos authentication. + * + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_cred_default_new(git_cred **out); /** * Signature of a function which acquires a credential object. * - * @param cred The newly created credential object. - * @param url The resource for which we are demanding a credential. - * @param username_from_url The username that was embedded in a "user@host" + * - cred: The newly created credential object. + * - url: The resource for which we are demanding a credential. + * - username_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. - * @return 0 for success or an error code for failure + * - allowed_types: A bitmask stating which cred types are OK to return. + * - payload: The payload provided when specifying this callback. + * - returns 0 for success or non-zero to indicate an error */ typedef int (*git_cred_acquire_cb)( git_cred **cred, @@ -150,39 +194,45 @@ typedef enum { GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1 } git_transport_flags_t; -typedef void (*git_transport_message_cb)(const char *str, int len, void *data); +typedef int (*git_transport_message_cb)(const char *str, int len, void *data); -typedef struct git_transport { +typedef struct git_transport git_transport; + +struct git_transport { unsigned int version; /* Set progress and error callbacks */ - int (*set_callbacks)(struct git_transport *transport, + int (*set_callbacks)( + git_transport *transport, git_transport_message_cb progress_cb, git_transport_message_cb error_cb, void *payload); /* Connect the transport to the remote repository, using the given * direction. */ - int (*connect)(struct git_transport *transport, + int (*connect)( + git_transport *transport, const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, int direction, int flags); - /* This function may be called after a successful call to connect(). The - * provided callback is invoked for each ref discovered on the remote - * end. */ - int (*ls)(struct git_transport *transport, - git_headlist_cb list_cb, - void *payload); + /* This function may be called after a successful call to + * connect(). The array returned is owned by the transport and + * is guranteed until the next call of a transport function. */ + int (*ls)( + const git_remote_head ***out, + size_t *size, + git_transport *transport); /* Executes the push whose context is in the git_push object. */ - int (*push)(struct git_transport *transport, git_push *push); + int (*push)(git_transport *transport, git_push *push); /* This function may be called after a successful call to connect(), when * the direction is FETCH. The function performs a negotiation to calculate * the wants list for the fetch. */ - int (*negotiate_fetch)(struct git_transport *transport, + int (*negotiate_fetch)( + git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count); @@ -190,28 +240,29 @@ typedef struct git_transport { /* This function may be called after a successful call to negotiate_fetch(), * when the direction is FETCH. This function retrieves the pack file for * the fetch from the remote end. */ - int (*download_pack)(struct git_transport *transport, + int (*download_pack)( + git_transport *transport, git_repository *repo, git_transfer_progress *stats, git_transfer_progress_callback progress_cb, void *progress_payload); /* Checks to see if the transport is connected */ - int (*is_connected)(struct git_transport *transport); + int (*is_connected)(git_transport *transport); /* Reads the flags value previously passed into connect() */ - int (*read_flags)(struct git_transport *transport, int *flags); + int (*read_flags)(git_transport *transport, int *flags); /* Cancels any outstanding transport operation */ - void (*cancel)(struct git_transport *transport); + void (*cancel)(git_transport *transport); /* This function is the reverse of connect() -- it terminates the * connection to the remote end. */ - int (*close)(struct git_transport *transport); + int (*close)(git_transport *transport); /* Frees/destructs the git_transport object. */ - void (*free)(struct git_transport *transport); -} git_transport; + void (*free)(git_transport *transport); +}; #define GIT_TRANSPORT_VERSION 1 #define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION} @@ -231,6 +282,40 @@ GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const /* Signature of a function which creates a transport */ typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param); +/** + * Add a custom transport definition, to be used in addition to the built-in + * set of transports that come with libgit2. + * + * The caller is responsible for synchronizing calls to git_transport_register + * and git_transport_unregister with other calls to the library that + * instantiate transports. + * + * @param prefix The scheme (ending in "://") to match, i.e. "git://" + * @param priority The priority of this transport relative to others with + * the same prefix. Built-in transports have a priority of 1. + * @param cb The callback used to create an instance of the transport + * @param param A fixed parameter to pass to cb at creation time + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_register( + const char *prefix, + unsigned priority, + git_transport_cb cb, + void *param); + +/** + * + * Unregister a custom transport definition which was previously registered + * with git_transport_register. + * + * @param prefix From the previous call to git_transport_register + * @param priority From the previous call to git_transport_register + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_unregister( + const char *prefix, + unsigned priority); + /* Transports which come with libgit2 (match git_transport_cb). The expected * value for "param" is listed in-line below. */ @@ -299,35 +384,36 @@ typedef enum { GIT_SERVICE_RECEIVEPACK = 4, } git_smart_service_t; -struct git_smart_subtransport; +typedef struct git_smart_subtransport git_smart_subtransport; +typedef struct git_smart_subtransport_stream git_smart_subtransport_stream; /* A stream used by the smart transport to read and write data * from a subtransport */ -typedef struct git_smart_subtransport_stream { +struct git_smart_subtransport_stream { /* The owning subtransport */ - struct git_smart_subtransport *subtransport; + git_smart_subtransport *subtransport; int (*read)( - struct git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read); + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read); int (*write)( - struct git_smart_subtransport_stream *stream, - const char *buffer, - size_t len); + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len); void (*free)( - struct git_smart_subtransport_stream *stream); -} git_smart_subtransport_stream; + git_smart_subtransport_stream *stream); +}; /* An implementation of a subtransport which carries data for the * smart transport */ -typedef struct git_smart_subtransport { +struct git_smart_subtransport { int (* action)( git_smart_subtransport_stream **out, - struct git_smart_subtransport *transport, + git_smart_subtransport *transport, const char *url, git_smart_service_t action); @@ -337,10 +423,10 @@ typedef struct git_smart_subtransport { * * 1. UPLOADPACK_LS -> UPLOADPACK * 2. RECEIVEPACK_LS -> RECEIVEPACK */ - int (* close)(struct git_smart_subtransport *transport); + int (*close)(git_smart_subtransport *transport); - void (* free)(struct git_smart_subtransport *transport); -} git_smart_subtransport; + void (*free)(git_smart_subtransport *transport); +}; /* A function which creates a new subtransport for the smart transport */ typedef int (*git_smart_subtransport_cb)( diff --git a/include/git2/tree.h b/include/git2/tree.h index 65d8cc16e..d94b446c2 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -198,6 +198,17 @@ GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry); */ GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); +/** + * Get the raw UNIX file attributes of a tree entry + * + * This function does not perform any normalization and is only useful + * if you need to be able to recreate the original tree object. + * + * @param entry a tree entry + * @return filemode as an integer + */ + +GIT_EXTERN(git_filemode_t) git_tree_entry_filemode_raw(const git_tree_entry *entry); /** * Compare two tree entries * @@ -208,7 +219,7 @@ GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2); /** - * Convert a tree entry to the git_object it points too. + * Convert a tree entry to the git_object it points to. * * You must call `git_object_free()` on the object when you are done with it. * diff --git a/include/git2/types.h b/include/git2/types.h index dc344075c..55505b110 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator; /** Merge heads, the input to merge */ typedef struct git_merge_head git_merge_head; +/** Merge result */ +typedef struct git_merge_result git_merge_result; + /** Representation of a status collection */ typedef struct git_status_list git_status_list; @@ -212,11 +215,21 @@ typedef struct git_remote_callbacks git_remote_callbacks; /** * This is passed as the first argument to the callback to allow the * user to see the progress. + * + * - total_objects: number of objects in the packfile being downloaded + * - indexed_objects: received objects that have been hashed + * - received_objects: objects which have been downloaded + * - local_objects: locally-available objects that have been injected + * in order to fix a thin pack. + * - received-bytes: size of the packfile received up to now */ typedef struct git_transfer_progress { unsigned int total_objects; unsigned int indexed_objects; unsigned int received_objects; + unsigned int local_objects; + unsigned int total_deltas; + unsigned int indexed_deltas; size_t received_bytes; } git_transfer_progress; @@ -229,6 +242,87 @@ typedef struct git_transfer_progress { */ typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload); +/** + * Opaque structure representing a submodule. + */ +typedef struct git_submodule git_submodule; + +/** + * Submodule update values + * + * These values represent settings for the `submodule.$name.update` + * configuration value which says how to handle `git submodule update` for + * this submodule. The value is usually set in the ".gitmodules" file and + * copied to ".git/config" when the submodule is initialized. + * + * You can override this setting on a per-submodule basis with + * `git_submodule_set_update()` and write the changed value to disk using + * `git_submodule_save()`. If you have overwritten the value, you can + * revert it by passing `GIT_SUBMODULE_UPDATE_RESET` to the set function. + * + * The values are: + * + * - GIT_SUBMODULE_UPDATE_RESET: reset to the on-disk value. + * - GIT_SUBMODULE_UPDATE_CHECKOUT: the default; when a submodule is + * updated, checkout the new detached HEAD to the submodule directory. + * - GIT_SUBMODULE_UPDATE_REBASE: update by rebasing the current checked + * out branch onto the commit from the superproject. + * - GIT_SUBMODULE_UPDATE_MERGE: update by merging the commit in the + * superproject into the current checkout out branch of the submodule. + * - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when + * the commit in the superproject is updated. + * - GIT_SUBMODULE_UPDATE_DEFAULT: not used except as static initializer + * when we don't want any particular update rule to be specified. + */ +typedef enum { + GIT_SUBMODULE_UPDATE_RESET = -1, + + GIT_SUBMODULE_UPDATE_CHECKOUT = 1, + GIT_SUBMODULE_UPDATE_REBASE = 2, + GIT_SUBMODULE_UPDATE_MERGE = 3, + GIT_SUBMODULE_UPDATE_NONE = 4, + + GIT_SUBMODULE_UPDATE_DEFAULT = 0 +} git_submodule_update_t; + +/** + * Submodule ignore values + * + * These values represent settings for the `submodule.$name.ignore` + * configuration value which says how deeply to look at the working + * directory when getting submodule status. + * + * You can override this value in memory on a per-submodule basis with + * `git_submodule_set_ignore()` and can write the changed value to disk + * with `git_submodule_save()`. If you have overwritten the value, you + * can revert to the on disk value by using `GIT_SUBMODULE_IGNORE_RESET`. + * + * The values are: + * + * - GIT_SUBMODULE_IGNORE_RESET: reset to the on-disk value. + * - GIT_SUBMODULE_IGNORE_NONE: don't ignore any change - i.e. even an + * untracked file, will mark the submodule as dirty. Ignored files are + * still ignored, of course. + * - GIT_SUBMODULE_IGNORE_UNTRACKED: ignore untracked files; only changes + * to tracked files, or the index or the HEAD commit will matter. + * - GIT_SUBMODULE_IGNORE_DIRTY: ignore changes in the working directory, + * only considering changes if the HEAD of submodule has moved from the + * value in the superproject. + * - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty + * - GIT_SUBMODULE_IGNORE_DEFAULT: not used except as static initializer + * when we don't want any particular ignore rule to be specified. + */ +typedef enum { + GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ + + GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 4, /* never dirty */ + + GIT_SUBMODULE_IGNORE_DEFAULT = 0 +} git_submodule_ignore_t; + /** @} */ GIT_END_DECL diff --git a/include/git2/version.h b/include/git2/version.h index d8a915fac..c4c5e8eb1 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,9 +7,9 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.19.0" +#define LIBGIT2_VERSION "0.20.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 19 +#define LIBGIT2_VER_MINOR 20 #define LIBGIT2_VER_REVISION 0 #endif diff --git a/libgit2.pc.in b/libgit2.pc.in index 52ad901f7..8f5279234 100644 --- a/libgit2.pc.in +++ b/libgit2.pc.in @@ -4,6 +4,7 @@ includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@ Name: libgit2 Description: The git library, take 2 Version: @LIBGIT2_VERSION_STRING@ -Requires: libcrypto -Libs: -L${libdir} -lgit2 -lz -lcrypto +Requires.private: @LIBGIT2_PC_REQUIRES@ +Libs.private: @LIBGIT2_PC_LIBS@ +Libs: -L${libdir} -lgit2 Cflags: -I${includedir} diff --git a/packaging/rpm/README b/packaging/rpm/README deleted file mode 100644 index 1a6410b16..000000000 --- a/packaging/rpm/README +++ /dev/null @@ -1,6 +0,0 @@ -To build RPM pakcages for Fedora, follow these steps: - cp packaging/rpm/libgit2.spec ~/rpmbuild/SPECS - cd ~/rpmbuild/SOURCES - wget https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz - cd ~/rpmbuild/SPECS - rpmbuild -ba libgit2.spec diff --git a/packaging/rpm/libgit2.spec b/packaging/rpm/libgit2.spec deleted file mode 100644 index 80e70c164..000000000 --- a/packaging/rpm/libgit2.spec +++ /dev/null @@ -1,106 +0,0 @@ -# -# spec file for package libgit2 -# -# Copyright (c) 2012 Saleem Ansari -# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. -# Copyright (c) 2011, Sascha Peilicke -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -# Please submit bugfixes or comments via http://bugs.opensuse.org/ -# -Name: libgit2 -Version: 0.16.0 -Release: 1 -Summary: C git library -License: GPL-2.0 with linking -Group: Development/Libraries/C and C++ -Url: http://libgit2.github.com/ -Source0: https://github.com/downloads/libgit2/libgit2/libgit2-0.16.0.tar.gz -BuildRequires: cmake -BuildRequires: pkgconfig -BuildRoot: %{_tmppath}/%{name}-%{version}-build -%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version} -BuildRequires: openssl-devel -%else -BuildRequires: libopenssl-devel -%endif - -%description -libgit2 is a portable, pure C implementation of the Git core methods -provided as a re-entrant linkable library with a solid API, allowing -you to write native speed custom Git applications in any language -with bindings. - -%package -n %{name}-0 -Summary: C git library -Group: System/Libraries - -%description -n %{name}-0 -libgit2 is a portable, pure C implementation of the Git core methods -provided as a re-entrant linkable library with a solid API, allowing -you to write native speed custom Git applications in any language -with bindings. - -%package devel -Summary: C git library -Group: Development/Libraries/C and C++ -Requires: %{name}-0 >= %{version} - -%description devel -This package contains all necessary include files and libraries needed -to compile and develop applications that use libgit2. - -%prep -%setup -q - -%build -cmake . \ - -DCMAKE_C_FLAGS:STRING="%{optflags}" \ - -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \ - -DLIB_INSTALL_DIR:PATH=%{_libdir}S -make %{?_smp_mflags} - -%install -%make_install - -%post -n %{name}-0 -p /sbin/ldconfig -%postun -n %{name}-0 -p /sbin/ldconfig - -%files -n %{name}-0 -%defattr (-,root,root) -%doc AUTHORS COPYING README.md -%{_libdir}/%{name}.so.* - -%files devel -%defattr (-,root,root) -%doc CONVENTIONS examples -%{_libdir}/%{name}.so -%{_includedir}/git2* -%{_libdir}/pkgconfig/libgit2.pc - -%changelog -* Tue Mar 04 2012 tuxdna@gmail.com -- Update to version 0.16.0 -* Tue Jan 31 2012 jengelh@medozas.de -- Provide pkgconfig symbols -* Thu Oct 27 2011 saschpe@suse.de -- Change license to 'GPL-2.0 with linking', fixes bnc#726789 -* Wed Oct 26 2011 saschpe@suse.de -- Update to version 0.15.0: - * Upstream doesn't provide changes -- Removed outdated %%clean section -* Tue Jan 18 2011 saschpe@gmx.de -- Proper Requires for devel package -* Tue Jan 18 2011 saschpe@gmx.de -- Set BuildRequires to "openssl-devel" also for RHEL and CentOS -* Tue Jan 18 2011 saschpe@gmx.de -- Initial commit (0.0.1) -- Added patch to fix shared library soname diff --git a/script/cibuild.sh b/script/cibuild.sh new file mode 100755 index 000000000..aa4fa47aa --- /dev/null +++ b/script/cibuild.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Create a test repo which we can use for the online::push tests +mkdir $HOME/_temp +git init --bare $HOME/_temp/test.git +git daemon --listen=localhost --export-all --enable=receive-pack --base-path=$HOME/_temp $HOME/_temp 2>/dev/null & +export GITTEST_REMOTE_URL="git://localhost/test.git" + +mkdir _build +cd _build +cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? +cmake --build . --target install || exit $? +ctest -V . || exit $? + +# Now that we've tested the raw git protocol, let's set up ssh to we +# can do the push tests over it + +killall git-daemon +sudo start ssh +ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q +cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys +ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts + +export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git" +export GITTEST_REMOTE_USER=$USER +export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" +export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" +export GITTEST_REMOTE_SSH_PASSPHRASE="" + +if [ -e ./libgit2_clar ]; then + ./libgit2_clar -sonline::push +fi diff --git a/src/array.h b/src/array.h index 2d77c71a0..1d4e1c224 100644 --- a/src/array.h +++ b/src/array.h @@ -30,37 +30,45 @@ #define git_array_init(a) \ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + #define git_array_clear(a) \ do { git__free((a).ptr); git_array_init(a); } while (0) #define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr) -typedef git_array_t(void) git_array_generic_t; +typedef git_array_t(char) git_array_generic_t; /* use a generic array for growth so this can return the new item */ -GIT_INLINE(void *) git_array_grow(git_array_generic_t *a, size_t item_size) +GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) { + git_array_generic_t *a = _a; uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2; - void *new_array = git__realloc(a->ptr, new_size * item_size); + char *new_array = git__realloc(a->ptr, new_size * item_size); if (!new_array) { git_array_clear(*a); return NULL; } else { a->ptr = new_array; a->asize = new_size; a->size++; - return (((char *)a->ptr) + (a->size - 1) * item_size); + return a->ptr + (a->size - 1) * item_size; } } #define git_array_alloc(a) \ - ((a).size >= (a).asize) ? \ - git_array_grow((git_array_generic_t *)&(a), sizeof(*(a).ptr)) : \ - (a).ptr ? &(a).ptr[(a).size++] : NULL + (((a).size >= (a).asize) ? \ + git_array_grow(&(a), sizeof(*(a).ptr)) : \ + ((a).ptr ? &(a).ptr[(a).size++] : NULL)) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL) +#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL) + #define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL) #define git_array_size(a) (a).size +#define git_array_valid_index(a, i) ((i) < (a).size) + #endif diff --git a/src/attr.c b/src/attr.c index 6cdff29f9..98a328a55 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,3 +1,4 @@ +#include "common.h" #include "repository.h" #include "fileops.h" #include "config.h" @@ -26,7 +27,6 @@ git_attr_t git_attr_value(const char *attr) return GIT_ATTR_VALUE_T; } - static int collect_attr_files( git_repository *repo, uint32_t flags, @@ -103,8 +103,6 @@ int git_attr_get_many( attr_get_many_info *info = NULL; size_t num_found = 0; - memset((void *)values, 0, sizeof(const char *) * num_attr); - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) return -1; @@ -141,6 +139,11 @@ int git_attr_get_many( } } + for (k = 0; k < num_attr; k++) { + if (!info[k].found) + values[k] = NULL; + } + cleanup: git_vector_free(&files); git_attr_path__free(&path); diff --git a/src/attr_file.c b/src/attr_file.c index d880398e8..4eb732436 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -39,7 +39,7 @@ int git_attr_file__new( attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3); GITERR_CHECK_ALLOC(attrs->key); - attrs->key[0] = '0' + from; + attrs->key[0] = '0' + (char)from; attrs->key[1] = '#'; memcpy(&attrs->key[2], path, len); attrs->key[len + 2] = '\0'; @@ -79,9 +79,13 @@ int git_attr_file__parse_buffer( while (!error && *scan) { /* allocate rule if needed */ - if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { - error = -1; - break; + if (!rule) { + if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = -1; + break; + } + rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | + GIT_ATTR_FNMATCH_ALLOWMACRO; } /* parse the next "pattern attr attr attr" line */ @@ -351,8 +355,8 @@ int git_attr_fnmatch__parse( if (parse_optimized_patterns(spec, pool, *base)) return 0; - spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE); - allow_space = (spec->flags != 0); + spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); + allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); pattern = *base; @@ -362,7 +366,7 @@ int git_attr_fnmatch__parse( return GIT_ENOTFOUND; } - if (*pattern == '[') { + if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { if (strncmp(pattern, "[attr]", 6) == 0) { spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; pattern += 6; @@ -370,7 +374,7 @@ int git_attr_fnmatch__parse( /* else a character range like [a-e]* which is accepted */ } - if (*pattern == '!') { + if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; pattern++; } diff --git a/src/attr_file.h b/src/attr_file.h index 15bba1c6a..3bc7c6cb8 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -28,6 +28,12 @@ #define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) #define GIT_ATTR_FNMATCH_ICASE (1U << 7) #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) +#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) +#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) + +#define GIT_ATTR_FNMATCH__INCOMING \ + (GIT_ATTR_FNMATCH_ALLOWSPACE | \ + GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) extern const char *git_attr__true; extern const char *git_attr__false; diff --git a/src/bitvec.h b/src/bitvec.h new file mode 100644 index 000000000..fd6f0ccf8 --- /dev/null +++ b/src/bitvec.h @@ -0,0 +1,75 @@ +/* + * 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_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "util.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint64_t *words; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; + } + + return 0; +} + +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); + + if (on) + *word |= mask; + else + *word &= ~mask; +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) + git__free(bv->u.words); +} + +#endif diff --git a/src/blame.c b/src/blame.c new file mode 100644 index 000000000..219a6bfe4 --- /dev/null +++ b/src/blame.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "blame.h" +#include "git2/commit.h" +#include "git2/revparse.h" +#include "git2/revwalk.h" +#include "git2/tree.h" +#include "git2/diff.h" +#include "git2/blob.h" +#include "git2/signature.h" +#include "util.h" +#include "repository.h" +#include "blame_git.h" + + +static int hunk_byfinalline_search_cmp(const void *key, const void *entry) +{ + uint16_t lineno = (uint16_t)*(size_t*)key; + git_blame_hunk *hunk = (git_blame_hunk*)entry; + + if (lineno < hunk->final_start_line_number) + return -1; + if (lineno >= hunk->final_start_line_number + hunk->lines_in_hunk) + return 1; + return 0; +} + +static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); } +static int hunk_cmp(const void *_a, const void *_b) +{ + git_blame_hunk *a = (git_blame_hunk*)_a, + *b = (git_blame_hunk*)_b; + + return a->final_start_line_number - b->final_start_line_number; +} + +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); +} + +static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) +{ + return line <= hunk->final_start_line_number; +} + +static git_blame_hunk* new_hunk( + uint16_t start, + uint16_t lines, + uint16_t orig_start, + const char *path) +{ + git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); + if (!hunk) return NULL; + + hunk->lines_in_hunk = lines; + hunk->final_start_line_number = start; + hunk->orig_start_line_number = orig_start; + hunk->orig_path = path ? git__strdup(path) : NULL; + + return hunk; +} + +static git_blame_hunk* dup_hunk(git_blame_hunk *hunk) +{ + git_blame_hunk *newhunk = new_hunk( + hunk->final_start_line_number, + hunk->lines_in_hunk, + hunk->orig_start_line_number, + hunk->orig_path); + git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id); + git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id); + newhunk->boundary = hunk->boundary; + newhunk->final_signature = git_signature_dup(hunk->final_signature); + newhunk->orig_signature = git_signature_dup(hunk->orig_signature); + return newhunk; +} + +static void free_hunk(git_blame_hunk *hunk) +{ + git__free((void*)hunk->orig_path); + git_signature_free(hunk->final_signature); + git_signature_free(hunk->orig_signature); + git__free(hunk); +} + +/* Starting with the hunk that includes start_line, shift all following hunks' + * final_start_line by shift_by lines */ +static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) +{ + size_t i; + + if (!git_vector_bsearch2( &i, v, hunk_byfinalline_search_cmp, &start_line)) { + for (; i < v->length; i++) { + git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; + hunk->final_start_line_number += shift_by; + } + } +} + +git_blame* git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path) +{ + git_blame *gbr = (git_blame*)git__calloc(1, sizeof(git_blame)); + if (!gbr) { + giterr_set_oom(); + return NULL; + } + git_vector_init(&gbr->hunks, 8, hunk_cmp); + git_vector_init(&gbr->paths, 8, paths_cmp); + gbr->repository = repo; + gbr->options = opts; + gbr->path = git__strdup(path); + git_vector_insert(&gbr->paths, git__strdup(path)); + return gbr; +} + +void git_blame_free(git_blame *blame) +{ + size_t i; + git_blame_hunk *hunk; + char *path; + + if (!blame) return; + + git_vector_foreach(&blame->hunks, i, hunk) + free_hunk(hunk); + git_vector_free(&blame->hunks); + + git_vector_foreach(&blame->paths, i, path) + git__free(path); + git_vector_free(&blame->paths); + + git_array_clear(blame->line_index); + + git__free((void*)blame->path); + git_blob_free(blame->final_blob); + git__free(blame); +} + +uint32_t git_blame_get_hunk_count(git_blame *blame) +{ + assert(blame); + return (uint32_t)blame->hunks.length; +} + +const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) +{ + assert(blame); + 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) +{ + size_t i; + assert(blame); + + if (!git_vector_bsearch2( &i, &blame->hunks, hunk_byfinalline_search_cmp, &lineno)) { + return git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + + return NULL; +} + +static void normalize_options( + git_blame_options *out, + const git_blame_options *in, + git_repository *repo) +{ + git_blame_options dummy = GIT_BLAME_OPTIONS_INIT; + if (!in) in = &dummy; + + memcpy(out, in, sizeof(git_blame_options)); + + /* No newest_commit => HEAD */ + if (git_oid_iszero(&out->newest_commit)) { + git_reference_name_to_id(&out->newest_commit, repo, "HEAD"); + } + + /* min_line 0 really means 1 */ + if (!out->min_line) out->min_line = 1; + /* max_line 0 really means N, but we don't know N yet */ + + /* Fix up option implications */ + if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE; +} + +static git_blame_hunk *split_hunk_in_vector( + git_vector *vec, + git_blame_hunk *hunk, + size_t rel_line, + bool return_new) +{ + size_t new_line_count; + git_blame_hunk *nh; + + /* Don't split if already at a boundary */ + if (rel_line <= 0 || + rel_line >= hunk->lines_in_hunk) + { + return hunk; + } + + 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); + git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id); + 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; + git_vector_insert_sorted(vec, nh, NULL); + { + git_blame_hunk *ret = return_new ? nh : hunk; + return ret; + } +} + +/* + * Construct a list of char indices for where lines begin + * Adapted from core git: + * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789 + */ +static int index_blob_lines(git_blame *blame) +{ + const char *buf = blame->final_buf; + git_off_t len = blame->final_buf_size; + int num = 0, incomplete = 0, bol = 1; + size_t *i; + + if (len && buf[len-1] != '\n') + incomplete++; /* incomplete line at the end */ + while (len--) { + if (bol) { + i = git_array_alloc(blame->line_index); + GITERR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + bol = 0; + } + if (*buf++ == '\n') { + num++; + bol = 1; + } + } + i = git_array_alloc(blame->line_index); + GITERR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + blame->num_lines = num + incomplete; + return blame->num_lines; +} + +static git_blame_hunk* hunk_from_entry(git_blame__entry *e) +{ + git_blame_hunk *h = new_hunk( + e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path); + git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); + h->final_signature = git_signature_dup(git_commit_author(e->suspect->commit)); + h->boundary = e->is_boundary ? 1 : 0; + return h; +} + +static int load_blob(git_blame *blame) +{ + int error; + + if (blame->final_blob) return 0; + + error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit); + if (error < 0) + goto cleanup; + error = git_object_lookup_bypath((git_object**)&blame->final_blob, + (git_object*)blame->final, blame->path, GIT_OBJ_BLOB); + if (error < 0) + goto cleanup; + +cleanup: + return error; +} + +static int blame_internal(git_blame *blame) +{ + int error; + git_blame__entry *ent = NULL; + git_blame__origin *o; + + if ((error = load_blob(blame)) < 0 || + (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0) + goto cleanup; + blame->final_buf = git_blob_rawcontent(blame->final_blob); + blame->final_buf_size = git_blob_rawsize(blame->final_blob); + + ent = git__calloc(1, sizeof(git_blame__entry)); + ent->num_lines = index_blob_lines(blame); + ent->lno = blame->options.min_line - 1; + ent->num_lines = ent->num_lines - blame->options.min_line + 1; + if (blame->options.max_line > 0) + ent->num_lines = blame->options.max_line - blame->options.min_line + 1; + ent->s_lno = ent->lno; + ent->suspect = o; + + blame->ent = ent; + blame->path = blame->path; + + git_blame__like_git(blame, blame->options.flags); + +cleanup: + for (ent = blame->ent; ent; ) { + git_blame__entry *e = ent->next; + + git_vector_insert(&blame->hunks, hunk_from_entry(ent)); + + git_blame__free_entry(ent); + ent = e; + } + + return error; +} + +/******************************************************************************* + * File blaming + ******************************************************************************/ + +int git_blame_file( + git_blame **out, + git_repository *repo, + const char *path, + git_blame_options *options) +{ + int error = -1; + git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + + assert(out && repo && path); + normalize_options(&normOptions, options, repo); + + blame = git_blame__alloc(repo, normOptions, path); + GITERR_CHECK_ALLOC(blame); + + if ((error = load_blob(blame)) < 0) + goto on_error; + + if ((error = blame_internal(blame)) < 0) + goto on_error; + + *out = blame; + return 0; + +on_error: + git_blame_free(blame); + return error; +} + +/******************************************************************************* + * Buffer blaming + *******************************************************************************/ + +static bool hunk_is_bufferblame(git_blame_hunk *hunk) +{ + return git_oid_iszero(&hunk->final_commit_id); +} + +static int buffer_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + uint32_t wedge_line; + + GIT_UNUSED(delta); + + wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; + blame->current_diff_line = wedge_line; + + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); + if (!blame->current_hunk) { + /* Line added at the end of the file */ + blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path); + git_vector_insert(&blame->hunks, blame->current_hunk); + } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ + /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ + blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, + wedge_line - blame->current_hunk->orig_start_line_number, true); + } + + return 0; +} + +static int ptrs_equal_cmp(const void *a, const void *b) { return ab ? 1 : 0; } +static int buffer_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + GIT_UNUSED(line); + + if (line->origin == GIT_DIFF_LINE_ADDITION) { + if (hunk_is_bufferblame(blame->current_hunk) && + hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { + /* Append to the current buffer-blame hunk */ + blame->current_hunk->lines_in_hunk++; + shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); + } 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); + git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); + } + blame->current_diff_line++; + } + + if (line->origin == GIT_DIFF_LINE_DELETION) { + /* Trim the line from the current hunk; remove it if it's now empty */ + size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; + + if (--(blame->current_hunk->lines_in_hunk) == 0) { + size_t i; + shift_base--; + if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { + git_vector_remove(&blame->hunks, i); + free_hunk(blame->current_hunk); + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + } + shift_hunks_by(&blame->hunks, shift_base, -1); + } + return 0; +} + +int git_blame_buffer( + git_blame **out, + git_blame *reference, + const char *buffer, + uint32_t buffer_len) +{ + git_blame *blame; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + size_t i; + git_blame_hunk *hunk; + + diffopts.context_lines = 0; + + assert(out && reference && buffer && buffer_len); + + blame = git_blame__alloc(reference->repository, reference->options, reference->path); + + /* Duplicate all of the hunk structures in the reference blame */ + git_vector_foreach(&reference->hunks, i, hunk) { + git_vector_insert(&blame->hunks, dup_hunk(hunk)); + } + + /* Diff to the reference blob */ + git_diff_blob_to_buffer(reference->final_blob, blame->path, + buffer, buffer_len, blame->path, + &diffopts, NULL, buffer_hunk_cb, buffer_line_cb, blame); + + *out = blame; + return 0; +} diff --git a/src/blame.h b/src/blame.h new file mode 100644 index 000000000..637e43985 --- /dev/null +++ b/src/blame.h @@ -0,0 +1,93 @@ +#ifndef INCLUDE_blame_h__ +#define INCLUDE_blame_h__ + +#include "git2/blame.h" +#include "common.h" +#include "vector.h" +#include "diff.h" +#include "array.h" +#include "git2/oid.h" + +/* + * One blob in a commit that is being suspected + */ +typedef struct git_blame__origin { + int refcnt; + struct git_blame__origin *previous; + git_commit *commit; + git_blob *blob; + char path[GIT_FLEX_ARRAY]; +} git_blame__origin; + +/* + * Each group of lines is described by a git_blame__entry; it can be split + * as we pass blame to the parents. They form a linked list in the + * scoreboard structure, sorted by the target line number. + */ +typedef struct git_blame__entry { + struct git_blame__entry *prev; + struct git_blame__entry *next; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + int lno; + + /* how many lines this group has */ + int num_lines; + + /* the commit that introduced this group into the final image */ + git_blame__origin *suspect; + + /* true if the suspect is truly guilty; false while we have not + * checked if the group came from one of its parents. + */ + bool guilty; + + /* true if the entry has been scanned for copies in the current parent + */ + bool scanned; + + /* 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; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over. + */ + unsigned score; + + /* Whether this entry has been tracked to a boundary commit. + */ + bool is_boundary; +} git_blame__entry; + +struct git_blame { + const char *path; + git_repository *repository; + git_blame_options options; + + git_vector hunks; + git_vector paths; + + git_blob *final_blob; + git_array_t(size_t) line_index; + + size_t current_diff_line; + git_blame_hunk *current_hunk; + + /* Scoreboard fields */ + git_commit *final; + git_blame__entry *ent; + int num_lines; + const char *final_buf; + git_off_t final_buf_size; +}; + +git_blame *git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path); + +#endif diff --git a/src/blame_git.c b/src/blame_git.c new file mode 100644 index 000000000..800f1f039 --- /dev/null +++ b/src/blame_git.c @@ -0,0 +1,622 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "blame_git.h" +#include "commit.h" +#include "blob.h" +#include "xdiff/xinclude.h" + +/* + * Origin is refcounted and usually we keep the blob contents to be + * reused. + */ +static git_blame__origin *origin_incref(git_blame__origin *o) +{ + if (o) + o->refcnt++; + return o; +} + +static void origin_decref(git_blame__origin *o) +{ + if (o && --o->refcnt <= 0) { + if (o->previous) + origin_decref(o->previous); + git_blob_free(o->blob); + git_commit_free(o->commit); + git__free(o); + } +} + +/* Given a commit and a path in it, create a new origin structure. */ +static int make_origin(git_blame__origin **out, git_commit *commit, const char *path) +{ + int error = 0; + git_blame__origin *o; + + o = git__calloc(1, sizeof(*o) + strlen(path) + 1); + GITERR_CHECK_ALLOC(o); + o->commit = commit; + o->refcnt = 1; + strcpy(o->path, path); + + if (!(error = git_object_lookup_bypath((git_object**)&o->blob, (git_object*)commit, + path, GIT_OBJ_BLOB))) { + *out = o; + } else { + origin_decref(o); + } + return error; +} + +/* Locate an existing origin or create a new one. */ +int git_blame__get_origin( + git_blame__origin **out, + git_blame *blame, + git_commit *commit, + const char *path) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) { + *out = origin_incref(e->suspect); + } + } + return make_origin(out, commit, path); +} + +typedef struct blame_chunk_cb_data { + git_blame *blame; + git_blame__origin *target; + git_blame__origin *parent; + long tlno; + long plno; +}blame_chunk_cb_data; + +static bool same_suspect(git_blame__origin *a, git_blame__origin *b) +{ + if (a == b) + return true; + if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit))) + return false; + return 0 == strcmp(a->path, b->path); +} + +/* 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) +{ + git_blame__entry *e; + int last_in_target = -1; + + 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) + last_in_target = e->s_lno + e->num_lines; + } + return last_in_target; +} + +/* + * It is known that lines between tlno to same came from parent, and e + * has an overlap with that range. it also is known that parent's + * line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> (entirely within) + * <------------> (extends past) + * <------------> (starts before) + * <------------------> (entirely encloses) + * + * Split e into potentially three parts; before this chunk, the chunk + * 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) +{ + int chunk_end_lno; + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on the parent */ + split[0].suspect = origin_incref(e->suspect); + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = origin_incref(e->suspect); + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } else { + chunk_end_lno = e->lno + e->num_lines; + } + split[1].num_lines = chunk_end_lno - split[1].lno; + + /* + * if it turns out there is nothing to blame the parent for, forget about + * the splitting. !split[1].suspect signals this. + */ + if (split[1].num_lines < 1) + return; + split[1].suspect = origin_incref(parent); +} + +/* + * Link in a new blame entry to the scoreboard. Entries that cover the same + * line range have been removed from the scoreboard previously. + */ +static void add_blame_entry(git_blame *blame, git_blame__entry *e) +{ + git_blame__entry *ent, *prev = NULL; + + origin_incref(e->suspect); + + for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next) + prev = ent; + + /* prev, if not NULL, is the last one that is below e */ + e->prev = prev; + if (prev) { + e->next = prev->next; + prev->next = e; + } else { + e->next = blame->ent; + blame->ent = e; + } + if (e->next) + e->next->prev = e; +} + +/* + * src typically is on-stack; we want to copy the information in it to + * a malloced blame_entry that is already on the linked list of the scoreboard. + * The origin of dst loses a refcnt while the origin of src gains one. + */ +static void dup_entry(git_blame__entry *dst, git_blame__entry *src) +{ + git_blame__entry *p, *n; + + p = dst->prev; + n = dst->next; + origin_incref(src->suspect); + origin_decref(dst->suspect); + memcpy(dst, src, sizeof(*src)); + dst->prev = p; + dst->next = n; + dst->score = 0; +} + +/* + * split_overlap() divided an existing blame e into up to three parts in split. + * Adjust the linked list of blames in the scoreboard to reflect the split. + */ +static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) +{ + git_blame__entry *new_entry; + + if (split[0].suspect && split[2].suspect) { + /* The first part (reuse storage for the existing entry e */ + dup_entry(e, &split[0]); + + /* The last part -- me */ + new_entry = git__malloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + + /* ... and the middle part -- parent */ + new_entry = git__malloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } else if (!split[0].suspect && !split[2].suspect) { + /* + * The parent covers the entire area; reuse storage for e and replace it + * with the parent + */ + dup_entry(e, &split[1]); + } else if (split[0].suspect) { + /* me and then parent */ + dup_entry(e, &split[0]); + new_entry = git__malloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } else { + /* parent and then me */ + dup_entry(e, &split[1]); + new_entry = git__malloc(sizeof(*new_entry)); + memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } +} + +/* + * After splitting the blame, the origins used by the on-stack blame_entry + * should lose one refcnt each. + */ +static void decref_split(git_blame__entry *split) +{ + int i; + for (i=0; i<3; i++) + origin_decref(split[i].suspect); +} + +/* + * Helper for blame_chunk(). blame_entry e is known to overlap with the patch + * hunk; split it and pass blame to the parent. + */ +static void blame_overlap( + git_blame *blame, + git_blame__entry *e, + int tlno, + int plno, + int same, + git_blame__origin *parent) +{ + git_blame__entry split[3] = {{0}}; + + split_overlap(split, e, tlno, plno, same, parent); + if (split[1].suspect) + split_blame(blame, split, e); + decref_split(split); +} + +/* + * Process one hunk from the patch between the current suspect for blame_entry + * e and its parent. Find and split the overlap, and pass blame to the + * overlapping part to the parent. + */ +static void blame_chunk( + git_blame *blame, + int tlno, + int plno, + int same, + git_blame__origin *target, + git_blame__origin *parent) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->guilty || !same_suspect(e->suspect, target)) + continue; + if (same <= e->s_lno) + continue; + if (tlno < e->s_lno + e->num_lines) { + blame_overlap(blame, e, tlno, plno, same, parent); + } + } +} + +static int my_emit( + xdfenv_t *xe, + xdchange_t *xscr, + xdemitcb_t *ecb, + xdemitconf_t const *xecfg) +{ + xdchange_t *xch = xscr; + GIT_UNUSED(xe); + GIT_UNUSED(xecfg); + while (xch) { + blame_chunk_cb_data *d = ecb->priv; + blame_chunk(d->blame, d->tlno, d->plno, xch->i2, d->target, d->parent); + d->plno = xch->i1 + xch->chg1; + d->tlno = xch->i2 + xch->chg2; + xch = xch->next; + } + return 0; +} + +static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx) +{ + const int blk = 1024; + long trimmed = 0, recovered = 0; + char *ap = a->ptr + a->size; + char *bp = b->ptr + b->size; + long smaller = (long)((a->size < b->size) ? a->size : b->size); + + if (ctx) + return; + + while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { + trimmed += blk; + ap -= blk; + bp -= blk; + } + + while (recovered < trimmed) + if (ap[recovered++] == '\n') + break; + a->size -= trimmed - recovered; + b->size -= trimmed - recovered; +} + +static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data) +{ + xpparam_t xpp = {0}; + xdemitconf_t xecfg = {0}; + xdemitcb_t ecb = {0}; + + xecfg.emit_func = (void(*)(void))my_emit; + ecb.priv = cb_data; + + trim_common_tail(&file_a, &file_b, 0); + return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); +} + +static void fill_origin_blob(git_blame__origin *o, mmfile_t *file) +{ + memset(file, 0, sizeof(*file)); + if (o->blob) { + file->ptr = (char*)git_blob_rawcontent(o->blob); + file->size = (size_t)git_blob_rawsize(o->blob); + } +} + +static int pass_blame_to_parent( + git_blame *blame, + git_blame__origin *target, + git_blame__origin *parent) +{ + int 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) + 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); + /* 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); + + return 0; +} + +static int paths_on_dup(void **old, void *new) +{ + GIT_UNUSED(old); + git__free(new); + return -1; +} + +static git_blame__origin* find_origin( + git_blame *blame, + git_commit *parent, + git_blame__origin *origin) +{ + git_blame__origin *porigin = NULL; + git_diff *difflist = NULL; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_tree *otree=NULL, *ptree=NULL; + + /* Get the trees from this commit and its parent */ + if (0 != git_commit_tree(&otree, origin->commit) || + 0 != git_commit_tree(&ptree, parent)) + goto cleanup; + + /* Configure the diff */ + diffopts.context_lines = 0; + diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK; + + /* Check to see if files we're interested have changed */ + diffopts.pathspec.count = blame->paths.length; + diffopts.pathspec.strings = (char**)blame->paths.contents; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + if (!git_diff_num_deltas(difflist)) { + /* No changes; copy data */ + git_blame__get_origin(&porigin, blame, parent, origin->path); + } else { + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + int i; + + /* Generate a full diff between the two trees */ + git_diff_free(difflist); + diffopts.pathspec.count = 0; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + /* Let diff find renames */ + findopts.flags = GIT_DIFF_FIND_RENAMES; + if (0 != git_diff_find_similar(difflist, &findopts)) + goto cleanup; + + /* Find one that matches */ + for (i=0; i<(int)git_diff_num_deltas(difflist); i++) { + const git_diff_delta *delta = git_diff_get_delta(difflist, i); + + if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path)) + { + git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path), + paths_on_dup); + make_origin(&porigin, parent, delta->old_file.path); + } + } + } + +cleanup: + git_diff_free(difflist); + git_tree_free(otree); + git_tree_free(ptree); + return porigin; +} + +/* + * The blobs of origin and porigin exactly match, so everything origin is + * suspected for can be blamed on the parent. + */ +static void pass_whole_blame(git_blame *blame, + git_blame__origin *origin, git_blame__origin *porigin) +{ + git_blame__entry *e; + + if (!porigin->blob) + git_object_lookup((git_object**)&porigin->blob, blame->repository, + git_blob_id(origin->blob), GIT_OBJ_BLOB); + for (e=blame->ent; e; e=e->next) { + if (!same_suspect(e->suspect, origin)) + continue; + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } +} + +static void 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; + + GIT_UNUSED(opt); + + num_parents = git_commit_parentcount(commit); + if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) + /* Stop at oldest specified commit */ + num_parents = 0; + if (!num_parents) { + git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit)); + goto finish; + } + else if (num_parents < (int)ARRAY_SIZE(sg_buf)) + memset(sg_buf, 0, sizeof(sg_buf)); + else + sg_origin = git__calloc(num_parents, sizeof(*sg_origin)); + + for (i=0; icommit, i); + porigin = find_origin(blame, p, origin); + + if (!porigin) + continue; + if (porigin->blob && origin->blob && + !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) { + pass_whole_blame(blame, origin, porigin); + origin_decref(porigin); + goto finish; + } + for (j = same = 0; jblob), git_blob_id(porigin->blob))) { + same = 1; + break; + } + if (!same) + sg_origin[i] = porigin; + else + origin_decref(porigin); + } + + /* Standard blame */ + for (i=0; iprevious) { + origin_incref(porigin); + origin->previous = porigin; + } + if (pass_blame_to_parent(blame, origin, porigin)) + goto finish; + } + + /* TODO: optionally find moves in parents' files */ + + /* TODO: optionally find copies in parents' files */ + +finish: + for (i=0; i pair), + * merge them together. + */ +static void coalesce(git_blame *blame) +{ + git_blame__entry *ent, *next; + + for (ent=blame->ent; ent && (next = ent->next); ent = next) { + if (same_suspect(ent->suspect, next->suspect) && + ent->guilty == next->guilty && + ent->s_lno + ent->num_lines == next->s_lno) + { + ent->num_lines += next->num_lines; + ent->next = next->next; + if (ent->next) + ent->next->prev = ent; + origin_decref(next->suspect); + git__free(next); + ent->score = 0; + next = ent; /* again */ + } + } +} + +void git_blame__like_git(git_blame *blame, uint32_t opt) +{ + while (true) { + git_blame__entry *ent; + git_blame__origin *suspect = NULL; + + /* Find a suspect to break down */ + for (ent = blame->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + return; /* 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); + + /* Take responsibility for the remaining entries */ + for (ent = blame->ent; ent; ent = ent->next) { + if (same_suspect(ent->suspect, suspect)) { + ent->guilty = true; + ent->is_boundary = !git_oid_cmp( + git_commit_id(suspect->commit), + &blame->options.oldest_commit); + } + } + origin_decref(suspect); + } + + coalesce(blame); +} + +void git_blame__free_entry(git_blame__entry *ent) +{ + if (!ent) return; + origin_decref(ent->suspect); + git__free(ent); +} diff --git a/src/blame_git.h b/src/blame_git.h new file mode 100644 index 000000000..3ec2710b8 --- /dev/null +++ b/src/blame_git.h @@ -0,0 +1,20 @@ +/* + * 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_blame_git__ +#define INCLUDE_blame_git__ + +#include "blame.h" + +int git_blame__get_origin( + git_blame__origin **out, + git_blame *sb, + 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); + +#endif diff --git a/src/blob.c b/src/blob.c index 2e4d5f479..2c6d52800 100644 --- a/src/blob.c +++ b/src/blob.c @@ -60,10 +60,10 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0) return error; - if ((error = stream->write(stream, buffer, len)) == 0) - error = stream->finalize_write(oid, stream); + if ((error = git_odb_stream_write(stream, buffer, len)) == 0) + error = git_odb_stream_finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_free(stream); return error; } @@ -80,12 +80,12 @@ static int write_file_stream( return error; if ((fd = git_futils_open_ro(path)) < 0) { - stream->free(stream); + git_odb_stream_free(stream); return -1; } while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - error = stream->write(stream, buffer, read_len); + error = git_odb_stream_write(stream, buffer, read_len); written += read_len; } @@ -97,36 +97,32 @@ static int write_file_stream( } if (!error) - error = stream->finalize_write(oid, stream); + error = git_odb_stream_finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_free(stream); return error; } static int write_file_filtered( git_oid *oid, + git_off_t *size, git_odb *odb, const char *full_path, - git_vector *filters) + git_filter_list *fl) { int error; - git_buf source = GIT_BUF_INIT; - git_buf dest = GIT_BUF_INIT; + git_buf tgt = GIT_BUF_INIT; - if ((error = git_futils_readbuffer(&source, full_path)) < 0) - return error; - - error = git_filters_apply(&dest, &source, filters); - - /* Free the source as soon as possible. This can be big in memory, - * and we don't want to ODB write to choke */ - git_buf_free(&source); + error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path); /* Write the file to disk if it was properly filtered */ - if (!error) - error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); + if (!error) { + *size = tgt.size; - git_buf_free(&dest); + error = git_odb_write(oid, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB); + } + + git_buf_free(&tgt); return error; } @@ -152,45 +148,67 @@ static int write_symlink( return error; } -static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) +int git_blob__create_from_paths( + git_oid *oid, + struct stat *out_st, + git_repository *repo, + const char *content_path, + const char *hint_path, + mode_t hint_mode, + bool try_load_filters) { int error; struct stat st; git_odb *odb = NULL; git_off_t size; + mode_t mode; + git_buf path = GIT_BUF_INIT; assert(hint_path || !try_load_filters); - if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; + if (!content_path) { + if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) + return GIT_EBAREREPO; + + if (git_buf_joinpath( + &path, git_repository_workdir(repo), hint_path) < 0) + return -1; + + content_path = path.ptr; + } + + if ((error = git_path_lstat(content_path, &st)) < 0 || + (error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (out_st) + memcpy(out_st, &st, sizeof(st)); size = st.st_size; + mode = hint_mode ? hint_mode : st.st_mode; - if (S_ISLNK(st.st_mode)) { + if (S_ISLNK(mode)) { error = write_symlink(oid, odb, content_path, (size_t)size); } else { - git_vector write_filters = GIT_VECTOR_INIT; - int filter_count = 0; + git_filter_list *fl = NULL; - if (try_load_filters) { + if (try_load_filters) /* Load the filters for writing this file to the ODB */ - filter_count = git_filters_load( - &write_filters, repo, hint_path, GIT_FILTER_TO_ODB); - } + error = git_filter_list_load( + &fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB); - if (filter_count < 0) { - /* Negative value means there was a critical error */ - error = filter_count; - } else if (filter_count == 0) { + if (error < 0) + /* well, that didn't work */; + else if (fl == NULL) /* No filters need to be applied to the document: we can stream * directly from disk */ error = write_file_stream(oid, odb, content_path, size); - } else { + else { /* We need to apply one or more filters */ - error = write_file_filtered(oid, odb, content_path, &write_filters); - } + error = write_file_filtered(oid, &size, odb, content_path, fl); - git_filters_free(&write_filters); + git_filter_list_free(fl); + } /* * TODO: eventually support streaming filtered files, for files @@ -207,34 +225,21 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char * */ } +done: + git_odb_free(odb); + git_buf_free(&path); + return error; } -int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path) +int git_blob_create_fromworkdir( + git_oid *oid, git_repository *repo, const char *path) { - git_buf full_path = GIT_BUF_INIT; - const char *workdir; - int error; - - if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0) - return error; - - workdir = git_repository_workdir(repo); - - if (git_buf_joinpath(&full_path, workdir, path) < 0) { - git_buf_free(&full_path); - return -1; - } - - error = blob_create_internal( - oid, repo, git_buf_cstr(&full_path), - git_buf_cstr(&full_path) + strlen(workdir), true); - - git_buf_free(&full_path); - return error; + return git_blob__create_from_paths(oid, NULL, repo, NULL, path, 0, true); } -int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path) +int git_blob_create_fromdisk( + git_oid *oid, git_repository *repo, const char *path) { int error; git_buf full_path = GIT_BUF_INIT; @@ -251,8 +256,8 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat if (workdir && !git__prefixcmp(hintpath, workdir)) hintpath += strlen(workdir); - error = blob_create_internal( - oid, repo, git_buf_cstr(&full_path), hintpath, true); + error = git_blob__create_from_paths( + oid, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true); git_buf_free(&full_path); return error; @@ -272,17 +277,14 @@ int git_blob_create_fromchunks( git_filebuf file = GIT_FILEBUF_INIT; git_buf path = GIT_BUF_INIT; - if (git_buf_join_n( - &path, '/', 3, - git_repository_path(repo), - GIT_OBJECTS_DIR, - "streamed") < 0) - goto cleanup; + if (git_buf_joinpath( + &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed") < 0) + goto cleanup; content = git__malloc(BUFFER_SIZE); GITERR_CHECK_ALLOC(content); - if (git_filebuf_open(&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY) < 0) + if (git_filebuf_open(&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, 0666) < 0) goto cleanup; while (1) { @@ -303,7 +305,8 @@ int git_blob_create_fromchunks( if (git_filebuf_flush(&file) < 0) goto cleanup; - error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); + error = git_blob__create_from_paths( + oid, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL); cleanup: git_buf_free(&path); @@ -318,8 +321,34 @@ int git_blob_is_binary(git_blob *blob) assert(blob); - content.ptr = blob->odb_object->buffer; - content.size = min(blob->odb_object->cached.size, 4000); + content.ptr = blob->odb_object->buffer; + content.size = min(blob->odb_object->cached.size, 4000); + content.asize = 0; return git_buf_text_is_binary(&content); } + +int git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *path, + int check_for_binary_data) +{ + int error = 0; + git_filter_list *fl = NULL; + + assert(blob && path && out); + + if (check_for_binary_data && git_blob_is_binary(blob)) + return 0; + + if (!(error = git_filter_list_load( + &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) { + + error = git_filter_list_apply_to_blob(out, fl, blob); + + git_filter_list_free(fl); + } + + return error; +} diff --git a/src/blob.h b/src/blob.h index 22e37cc3a..4cd9f1e0c 100644 --- a/src/blob.h +++ b/src/blob.h @@ -21,4 +21,13 @@ void git_blob__free(void *blob); int git_blob__parse(void *blob, git_odb_object *obj); int git_blob__getbuf(git_buf *buffer, git_blob *blob); +extern int git_blob__create_from_paths( + git_oid *out_oid, + struct stat *out_st, + git_repository *repo, + const char *full_path, + const char *hint_path, + mode_t hint_mode, + bool apply_filters); + #endif diff --git a/src/branch.c b/src/branch.c index 7064fa7fc..95b3fd980 100644 --- a/src/branch.c +++ b/src/branch.c @@ -124,50 +124,68 @@ on_error: return error; } -int git_branch_foreach( - git_repository *repo, - unsigned int list_flags, - git_branch_foreach_cb callback, - void *payload) -{ +typedef struct { git_reference_iterator *iter; + unsigned int flags; +} branch_iter; + +int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; git_reference *ref; - int error = 0; + int error; - if (git_reference_iterator_new(&iter, repo) < 0) - return -1; + while ((error = git_reference_next(&ref, iter->iter)) == 0) { + if ((iter->flags & GIT_BRANCH_LOCAL) && + !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_LOCAL; - while ((error = git_reference_next(&ref, iter)) == 0) { - if (list_flags & GIT_BRANCH_LOCAL && - git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0) { - if (callback(ref->name + strlen(GIT_REFS_HEADS_DIR), - GIT_BRANCH_LOCAL, payload)) { - error = GIT_EUSER; - } + return 0; + } else if ((iter->flags & GIT_BRANCH_REMOTE) && + !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_REMOTE; + + return 0; + } else { + git_reference_free(ref); } - - if (list_flags & GIT_BRANCH_REMOTE && - git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0) { - if (callback(ref->name + strlen(GIT_REFS_REMOTES_DIR), - GIT_BRANCH_REMOTE, payload)) { - error = GIT_EUSER; - } - } - - git_reference_free(ref); - - /* check if the callback has cancelled iteration */ - if (error == GIT_EUSER) - break; } - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); return error; } +int git_branch_iterator_new( + git_branch_iterator **out, + git_repository *repo, + git_branch_t list_flags) +{ + branch_iter *iter; + + iter = git__calloc(1, sizeof(branch_iter)); + GITERR_CHECK_ALLOC(iter); + + iter->flags = list_flags; + + if (git_reference_iterator_new(&iter->iter, repo) < 0) { + git__free(iter); + return -1; + } + + *out = (git_branch_iterator *) iter; + + return 0; +} + +void git_branch_iterator_free(git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; + + git_reference_iterator_free(iter->iter); + git__free(iter); +} + int git_branch_move( git_reference **out, git_reference *branch, @@ -585,7 +603,7 @@ int git_branch_is_head( error = git_repository_head(&head, git_reference_owner(branch)); - if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND) + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) return false; if (error < 0) diff --git a/src/buf_text.c b/src/buf_text.c index 443454b5f..631feb3f8 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -70,10 +70,10 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src) assert(tgt != src); if (!next) - return GIT_ENOTFOUND; + return git_buf_set(tgt, src->ptr, src->size); /* reduce reallocs while in the loop */ - if (git_buf_grow(tgt, src->size) < 0) + if (git_buf_grow(tgt, src->size + 1) < 0) return -1; out = tgt->ptr; tgt->size = 0; @@ -81,20 +81,25 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src) /* Find the next \r and copy whole chunk up to there to tgt */ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { if (next > scan) { - size_t copylen = next - scan; + size_t copylen = (size_t)(next - scan); memcpy(out, scan, copylen); out += copylen; } /* Do not drop \r unless it is followed by \n */ - if (next[1] != '\n') + if (next + 1 == scan_end || next[1] != '\n') *out++ = '\r'; } /* Copy remaining input into dest */ - memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */ - out += (scan_end - scan); - tgt->size = out - tgt->ptr; + if (scan < scan_end) { + size_t remaining = (size_t)(scan_end - scan); + memcpy(out, scan, remaining); + out += remaining; + } + + tgt->size = (size_t)(out - tgt->ptr); + tgt->ptr[tgt->size] = '\0'; return 0; } @@ -109,7 +114,7 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src) assert(tgt != src); if (!next) - return GIT_ENOTFOUND; + return git_buf_set(tgt, src->ptr, src->size); /* attempt to reduce reallocs while in the loop */ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0) @@ -170,8 +175,14 @@ int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings) bool git_buf_text_is_binary(const git_buf *buf) { const char *scan = buf->ptr, *end = buf->ptr + buf->size; + git_bom_t bom; int printable = 0, nonprintable = 0; + scan += git_buf_text_detect_bom(&bom, buf, 0); + + if (bom > GIT_BOM_UTF8) + return 1; + while (scan < end) { unsigned char c = *scan++; @@ -262,7 +273,7 @@ bool git_buf_text_gather_stats( while (scan < end) { unsigned char c = *scan++; - if ((c > 0x1F && c < 0x7F) || c > 0x9f) + if (c > 0x1F && c != 0x7F) stats->printable++; else switch (c) { case '\0': diff --git a/src/buf_text.h b/src/buf_text.h index 58e4e26a7..3ac9d1443 100644 --- a/src/buf_text.h +++ b/src/buf_text.h @@ -56,16 +56,16 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string) extern void git_buf_text_unescape(git_buf *buf); /** - * Replace all \r\n with \n (or do nothing if no \r\n are found) + * Replace all \r\n with \n. Does not modify \r without trailing \n. * - * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error + * @return 0 on success, -1 on memory error */ extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src); /** - * Replace all \n with \r\n (or do nothing if no \n are found) + * Replace all \n with \r\n. Does not modify existing \r\n. * - * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error + * @return 0 on success, -1 on memory error */ extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src); diff --git a/src/buffer.c b/src/buffer.c index 6e3ffe560..20682322e 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -6,6 +6,7 @@ */ #include "buffer.h" #include "posix.h" +#include "git2/buffer.h" #include #include @@ -31,7 +32,8 @@ void git_buf_init(git_buf *buf, size_t initial_size) git_buf_grow(buf, initial_size); } -int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) +int git_buf_try_grow( + git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external) { char *new_ptr; size_t new_size; @@ -39,6 +41,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) if (buf->ptr == git_buf__oom) return -1; + if (!target_size) + target_size = buf->size; + if (target_size <= buf->asize) return 0; @@ -66,6 +71,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) return -1; } + if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0) + memcpy(new_ptr, buf->ptr, min(buf->size, new_size)); + buf->asize = new_size; buf->ptr = new_ptr; @@ -77,11 +85,16 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) return 0; } +int git_buf_grow(git_buf *buffer, size_t target_size) +{ + return git_buf_try_grow(buffer, target_size, true, true); +} + void git_buf_free(git_buf *buf) { if (!buf) return; - if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom) + if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom) git__free(buf->ptr); git_buf_init(buf, 0); @@ -90,11 +103,15 @@ void git_buf_free(git_buf *buf) void git_buf_clear(git_buf *buf) { buf->size = 0; + + if (!buf->ptr) + buf->ptr = git_buf__initbuf; + if (buf->asize > 0) buf->ptr[0] = '\0'; } -int git_buf_set(git_buf *buf, const char *data, size_t len) +int git_buf_set(git_buf *buf, const void *data, size_t len) { if (len == 0 || data == NULL) { git_buf_clear(buf); @@ -137,7 +154,7 @@ int git_buf_puts(git_buf *buf, const char *string) return git_buf_put(buf, string, strlen(string)); } -static const char b64str[64] = +static const char b64str[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int git_buf_put_base64(git_buf *buf, const char *data, size_t len) @@ -194,6 +211,8 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) format, args ); + va_end(args); + if (len < 0) { git__free(buf->ptr); buf->ptr = git_buf__oom; @@ -259,6 +278,15 @@ void git_buf_truncate(git_buf *buf, size_t len) } } +void git_buf_shorten(git_buf *buf, size_t amount) +{ + if (amount > buf->size) + amount = buf->size; + + buf->size = buf->size - amount; + buf->ptr[buf->size] = '\0'; +} + void git_buf_rtruncate_at_char(git_buf *buf, char separator) { ssize_t idx = git_buf_rfind_next(buf, separator); diff --git a/src/buffer.h b/src/buffer.h index 5402f3827..4ca9d4d94 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -9,18 +9,26 @@ #include "common.h" #include "git2/strarray.h" +#include "git2/buffer.h" #include -typedef struct { - char *ptr; - size_t asize, size; -} git_buf; +/* typedef struct { + * char *ptr; + * size_t asize, size; + * } git_buf; + */ extern char git_buf__initbuf[]; extern char git_buf__oom[]; +/* Use to initialize buffer structure when git_buf is on stack */ #define GIT_BUF_INIT { git_buf__initbuf, 0, 0 } +GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf) +{ + return (buf->ptr != NULL && buf->asize > 0); +} + /** * Initialize a git_buf structure. * @@ -32,27 +40,16 @@ extern void git_buf_init(git_buf *buf, size_t initial_size); /** * Attempt to grow the buffer to hold at least `target_size` bytes. * - * If the allocation fails, this will return an error. If mark_oom is true, + * If the allocation fails, this will return an error. If `mark_oom` is true, * this will mark the buffer as invalid for future operations; if false, * existing buffer content will be preserved, but calling code must handle - * that buffer was not expanded. + * that buffer was not expanded. If `preserve_external` is true, then any + * existing data pointed to be `ptr` even if `asize` is zero will be copied + * into the newly allocated buffer. */ -extern int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom); +extern int git_buf_try_grow( + git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external); -/** - * Grow the buffer to hold at least `target_size` bytes. - * - * If the allocation fails, this will return an error and the buffer will be - * marked as invalid for future operations, invaliding contents. - * - * @return 0 on success or -1 on failure - */ -GIT_INLINE(int) git_buf_grow(git_buf *buf, size_t target_size) -{ - return git_buf_try_grow(buf, target_size, true); -} - -extern void git_buf_free(git_buf *buf); extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b); extern char *git_buf_detach(git_buf *buf); extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize); @@ -81,7 +78,6 @@ GIT_INLINE(bool) git_buf_oom(const git_buf *buf) * return code of these functions and call them in a series then just call * git_buf_oom at the end. */ -int git_buf_set(git_buf *buf, const char *data, size_t len); int git_buf_sets(git_buf *buf, const char *string); int git_buf_putc(git_buf *buf, char c); int git_buf_put(git_buf *buf, const char *data, size_t len); @@ -91,6 +87,7 @@ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap); void git_buf_clear(git_buf *buf); void git_buf_consume(git_buf *buf, const char *end); void git_buf_truncate(git_buf *buf, size_t len); +void git_buf_shorten(git_buf *buf, size_t amount); void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); diff --git a/src/cc-compat.h b/src/cc-compat.h index a5e4ce17e..37f1ea81e 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -54,8 +54,12 @@ #if defined (_MSC_VER) typedef unsigned char bool; -# define true 1 -# define false 0 +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif #else # include #endif diff --git a/src/checkout.c b/src/checkout.c index 8f9ec64e4..6d7e3cfd4 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -26,6 +26,8 @@ #include "diff.h" #include "pathspec.h" #include "buf_text.h" +#include "merge_file.h" +#include "path.h" /* See docs/checkout-internals.md for more information */ @@ -35,21 +37,23 @@ enum { CHECKOUT_ACTION__UPDATE_BLOB = 2, CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__MAX = 8, - CHECKOUT_ACTION__DEFER_REMOVE = 16, + CHECKOUT_ACTION__UPDATE_CONFLICT = 16, + CHECKOUT_ACTION__MAX = 16, + CHECKOUT_ACTION__DEFER_REMOVE = 32, CHECKOUT_ACTION__REMOVE_AND_UPDATE = (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE), }; typedef struct { git_repository *repo; - git_diff_list *diff; + git_diff *diff; git_checkout_opts opts; bool opts_free_baseline; char *pfx; git_index *index; git_pool pool; git_vector removes; + git_vector conflicts; git_buf path; size_t workdir_len; unsigned int strategy; @@ -59,6 +63,16 @@ typedef struct { size_t completed_steps; } checkout_data; +typedef struct { + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; + + int name_collision:1, + directoryfile:1, + one_to_two:1; +} checkout_conflictdata; + static int checkout_notify( checkout_data *data, git_checkout_notify_t why, @@ -246,10 +260,10 @@ static int checkout_action_wd_only( bool remove = false; git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - if (!git_pathspec_match_path( + if (!git_pathspec__match( pathspec, wd->path, (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL)) + git_iterator_ignore_case(workdir), NULL, NULL)) return 0; /* check if item is tracked in the index but not in the checkout diff */ @@ -592,6 +606,359 @@ static int checkout_remaining_wd_items( return error; } +GIT_INLINE(int) checkout_idxentry_cmp( + const git_index_entry *a, + const git_index_entry *b) +{ + if (!a && !b) + return 0; + else if (!a && b) + return -1; + else if(a && !b) + return 1; + else + return strcmp(a->path, b->path); +} + +static int checkout_conflictdata_cmp(const void *a, const void *b) +{ + const checkout_conflictdata *ca = a; + const checkout_conflictdata *cb = b; + int diff; + + if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && + (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) + diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); + + return diff; +} + +int checkout_conflictdata_empty(const git_vector *conflicts, size_t idx) +{ + checkout_conflictdata *conflict; + + if ((conflict = git_vector_get(conflicts, idx)) == NULL) + return -1; + + if (conflict->ancestor || conflict->ours || conflict->theirs) + return 0; + + git__free(conflict); + return 1; +} + +GIT_INLINE(bool) conflict_pathspec_match( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs) +{ + /* if the pathspec matches ours *or* theirs, proceed */ + if (ours && git_pathspec__match(pathspec, ours->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (theirs && git_pathspec__match(pathspec, theirs->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (ancestor && git_pathspec__match(pathspec, ancestor->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + return false; +} + +static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) +{ + git_index_conflict_iterator *iterator = NULL; + const git_index_entry *ancestor, *ours, *theirs; + checkout_conflictdata *conflict; + int error = 0; + + if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0) + goto done; + + data->conflicts._cmp = checkout_conflictdata_cmp; + + /* Collect the conflicts */ + while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { + if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) + continue; + + conflict = git__calloc(1, sizeof(checkout_conflictdata)); + GITERR_CHECK_ALLOC(conflict); + + conflict->ancestor = ancestor; + conflict->ours = ours; + conflict->theirs = theirs; + + git_vector_insert(&data->conflicts, conflict); + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_index_conflict_iterator_free(iterator); + + return error; +} + +GIT_INLINE(int) checkout_conflicts_cmp_entry( + const char *path, + const git_index_entry *entry) +{ + return strcmp((const char *)path, entry->path); +} + +static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) +{ + const char *path = p; + const checkout_conflictdata *conflict = c; + + if (!conflict->ancestor) + return 1; + + return checkout_conflicts_cmp_entry(path, conflict->ancestor); +} + +static checkout_conflictdata *checkout_conflicts_search_ancestor( + checkout_data *data, + const char *path) +{ + size_t pos; + + if (git_vector_bsearch2(&pos, &data->conflicts, checkout_conflicts_cmp_ancestor, path) < 0) + return NULL; + + return git_vector_get(&data->conflicts, pos); +} + +static checkout_conflictdata *checkout_conflicts_search_branch( + checkout_data *data, + const char *path) +{ + checkout_conflictdata *conflict; + size_t i; + + git_vector_foreach(&data->conflicts, i, conflict) { + int cmp = -1; + + if (conflict->ancestor) + break; + + if (conflict->ours) + cmp = checkout_conflicts_cmp_entry(path, conflict->ours); + else if (conflict->theirs) + cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); + + if (cmp == 0) + return conflict; + } + + return NULL; +} + +static int checkout_conflicts_load_byname_entry( + checkout_conflictdata **ancestor_out, + checkout_conflictdata **ours_out, + checkout_conflictdata **theirs_out, + checkout_data *data, + const git_index_name_entry *name_entry) +{ + checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; + int error = 0; + + *ancestor_out = NULL; + *ours_out = NULL; + *theirs_out = NULL; + + if (!name_entry->ancestor) { + giterr_set(GITERR_INDEX, "A NAME entry exists without an ancestor"); + error = -1; + goto done; + } + + if (!name_entry->ours && !name_entry->theirs) { + giterr_set(GITERR_INDEX, "A NAME entry exists without an ours or theirs"); + error = -1; + goto done; + } + + if ((ancestor = checkout_conflicts_search_ancestor(data, + name_entry->ancestor)) == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced ancestor entry '%s' which does not exist in the main index", + name_entry->ancestor); + error = -1; + goto done; + } + + if (name_entry->ours) { + if (strcmp(name_entry->ancestor, name_entry->ours) == 0) + ours = ancestor; + else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || + ours->ours == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced our entry '%s' which does not exist in the main index", + name_entry->ours); + error = -1; + goto done; + } + } + + if (name_entry->theirs) { + if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) + theirs = ancestor; + else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) + theirs = ours; + else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || + theirs->theirs == NULL) { + giterr_set(GITERR_INDEX, + "A NAME entry referenced their entry '%s' which does not exist in the main index", + name_entry->theirs); + error = -1; + goto done; + } + } + + *ancestor_out = ancestor; + *ours_out = ours; + *theirs_out = theirs; + +done: + return error; +} + +static int checkout_conflicts_coalesce_renames( + checkout_data *data) +{ + const git_index_name_entry *name_entry; + checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; + size_t i, names; + int error = 0; + + /* Juggle entries based on renames */ + names = git_index_name_entrycount(data->index); + + for (i = 0; i < names; i++) { + name_entry = git_index_name_get_byindex(data->index, i); + + if ((error = checkout_conflicts_load_byname_entry( + &ancestor_conflict, &our_conflict, &their_conflict, + data, name_entry)) < 0) + goto done; + + if (our_conflict && our_conflict != ancestor_conflict) { + ancestor_conflict->ours = our_conflict->ours; + our_conflict->ours = NULL; + + if (our_conflict->theirs) + our_conflict->name_collision = 1; + + if (our_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (their_conflict && their_conflict != ancestor_conflict) { + ancestor_conflict->theirs = their_conflict->theirs; + their_conflict->theirs = NULL; + + if (their_conflict->ours) + their_conflict->name_collision = 1; + + if (their_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (our_conflict && our_conflict != ancestor_conflict && + their_conflict && their_conflict != ancestor_conflict) + ancestor_conflict->one_to_two = 1; + } + + git_vector_remove_matching(&data->conflicts, checkout_conflictdata_empty); + +done: + return error; +} + +static int checkout_conflicts_mark_directoryfile( + checkout_data *data) +{ + checkout_conflictdata *conflict; + const git_index_entry *entry; + size_t i, j, len; + const char *path; + int prefixed, error = 0; + + len = git_index_entrycount(data->index); + + /* Find d/f conflicts */ + git_vector_foreach(&data->conflicts, i, conflict) { + if ((conflict->ours && conflict->theirs) || + (!conflict->ours && !conflict->theirs)) + continue; + + path = conflict->ours ? + conflict->ours->path : conflict->theirs->path; + + if ((error = git_index_find(&j, data->index, path)) < 0) { + if (error == GIT_ENOTFOUND) + giterr_set(GITERR_INDEX, + "Index inconsistency, could not find entry for expected conflict '%s'", path); + + goto done; + } + + for (; j < len; j++) { + if ((entry = git_index_get_byindex(data->index, j)) == NULL) { + giterr_set(GITERR_INDEX, + "Index inconsistency, truncated index while loading expected conflict '%s'", path); + error = -1; + goto done; + } + + prefixed = git_path_equal_or_prefixed(path, entry->path); + + if (prefixed == GIT_PATH_EQUAL) + continue; + + if (prefixed == GIT_PATH_PREFIX) + conflict->directoryfile = 1; + + break; + } + } + +done: + return error; +} + +static int checkout_get_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + int error = 0; + + if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) + return 0; + + if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || + (error = checkout_conflicts_coalesce_renames(data)) < 0 || + (error = checkout_conflicts_mark_directoryfile(data)) < 0) + goto done; + +done: + return error; +} + static int checkout_get_actions( uint32_t **actions_ptr, size_t **counts_ptr, @@ -607,7 +974,7 @@ static int checkout_get_actions( uint32_t *actions = NULL; if (data->opts.paths.count > 0 && - git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) + git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; if ((error = git_iterator_current(&wditem, workdir)) < 0 && @@ -659,7 +1026,13 @@ static int checkout_get_actions( goto fail; } - git_pathspec_free(&pathspec); + + if ((error = checkout_get_conflicts(data, workdir, &pathspec)) < 0) + goto fail; + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts); + + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return 0; @@ -670,7 +1043,7 @@ fail: *actions_ptr = NULL; git__free(actions); - git_pathspec_free(&pathspec); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return error; @@ -678,7 +1051,7 @@ fail: static int buffer_to_file( struct stat *st, - git_buf *buffer, + git_buf *buf, const char *path, mode_t dir_mode, int file_open_flags, @@ -690,80 +1063,52 @@ static int buffer_to_file( return error; if ((error = git_futils_writebuffer( - buffer, path, file_open_flags, file_mode)) < 0) + buf, path, file_open_flags, file_mode)) < 0) return error; - if (st != NULL && (error = p_stat(path, st)) < 0) { - giterr_set(GITERR_OS, "Error while statting '%s'", path); - return error; - } + if (st != NULL && (error = p_stat(path, st)) < 0) + giterr_set(GITERR_OS, "Error statting '%s'", path); - if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) { + else if (GIT_PERMS_IS_EXEC(file_mode) && + (error = p_chmod(path, file_mode)) < 0) giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); - return error; - } - return 0; + return error; } static int blob_content_to_file( struct stat *st, git_blob *blob, const char *path, + const char * hint_path, mode_t entry_filemode, git_checkout_opts *opts) { - int error = -1, nb_filters = 0; - mode_t file_mode = opts->file_mode; - bool dont_free_filtered; - git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; - git_vector filters = GIT_VECTOR_INIT; + int error = 0; + mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode; + git_buf out = GIT_BUF_INIT; + git_filter_list *fl = NULL; - /* Create a fake git_buf from the blob raw data... */ - filtered.ptr = (void *)git_blob_rawcontent(blob); - filtered.size = (size_t)git_blob_rawsize(blob); - /* ... and make sure it doesn't get unexpectedly freed */ - dont_free_filtered = true; + if (hint_path == NULL) + hint_path = path; - if (!opts->disable_filters && - !git_buf_text_is_binary(&filtered) && - (nb_filters = git_filters_load( - &filters, - git_object_owner((git_object *)blob), - path, - GIT_FILTER_TO_WORKTREE)) > 0) - { - /* reset 'filtered' so it can be a filter target */ - git_buf_init(&filtered, 0); - dont_free_filtered = false; - } - - if (nb_filters < 0) - return nb_filters; - - if (nb_filters > 0) { - if ((error = git_blob__getbuf(&unfiltered, blob)) < 0) - goto cleanup; - - if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0) - goto cleanup; - } - - /* Allow overriding of file mode */ - if (!file_mode) - file_mode = entry_filemode; - - error = buffer_to_file( - st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); + if (!opts->disable_filters) + error = git_filter_list_load( + &fl, git_blob_owner(blob), blob, hint_path, GIT_FILTER_TO_WORKTREE); if (!error) + error = git_filter_list_apply_to_blob(&out, fl, blob); + + git_filter_list_free(fl); + + if (!error) { + error = buffer_to_file( + st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode); + st->st_mode = entry_filemode; -cleanup: - git_filters_free(&filters); - git_buf_free(&unfiltered); - if (!dont_free_filtered) - git_buf_free(&filtered); + git_buf_free(&out); + } return error; } @@ -815,7 +1160,8 @@ static int checkout_update_index( memset(&entry, 0, sizeof(entry)); entry.path = (char *)file->path; /* cast to prevent warning */ - git_index_entry__init_from_stat(&entry, st); + git_index_entry__init_from_stat( + &entry, st, !(git_index_caps(data->index) & GIT_INDEXCAP_NO_FILEMODE)); git_oid_cpy(&entry.oid, &file->oid); return git_index_add(data->index, &entry); @@ -917,34 +1263,26 @@ static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) return 0; } -static int checkout_blob( +static int checkout_write_content( checkout_data *data, - const git_diff_file *file) + const git_oid *oid, + const char *full_path, + const char *hint_path, + unsigned int mode, + struct stat *st) { int error = 0; git_blob *blob; - struct stat st; - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, file->path) < 0) - return -1; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( - git_buf_cstr(&data->path), file->mode); - if (rval <= 0) - return rval; - } - - if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) + if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) return error; - if (S_ISLNK(file->mode)) + if (S_ISLNK(mode)) error = blob_content_to_link( - &st, blob, git_buf_cstr(&data->path), data->opts.dir_mode, data->can_symlink); + st, blob, full_path, data->opts.dir_mode, data->can_symlink); else error = blob_content_to_file( - &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts); + st, blob, full_path, hint_path, mode, &data->opts); git_blob_free(blob); @@ -959,6 +1297,30 @@ static int checkout_blob( error = 0; } + return error; +} + +static int checkout_blob( + checkout_data *data, + const git_diff_file *file) +{ + int error = 0; + struct stat st; + + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, file->path) < 0) + return -1; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { + int rval = checkout_safe_for_update_only( + git_buf_cstr(&data->path), file->mode); + if (rval <= 0) + return rval; + } + + error = checkout_write_content( + data, &file->oid, git_buf_cstr(&data->path), NULL, file->mode, &st); + /* update the index unless prevented */ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) error = checkout_update_index(data, file, &st); @@ -1129,8 +1491,278 @@ static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) return error; } + +static int conflict_entry_name( + git_buf *out, + const char *side_name, + const char *filename) +{ + if (git_buf_puts(out, side_name) < 0 || + git_buf_putc(out, ':') < 0 || + git_buf_puts(out, filename) < 0) + return -1; + + return 0; +} + +static int checkout_path_suffixed(git_buf *path, const char *suffix) +{ + size_t path_len; + int i = 0, error = 0; + + if ((error = git_buf_putc(path, '~')) < 0 || (error = git_buf_puts(path, suffix)) < 0) + return -1; + + path_len = git_buf_len(path); + + while (git_path_exists(git_buf_cstr(path)) && i < INT_MAX) { + git_buf_truncate(path, path_len); + + if ((error = git_buf_putc(path, '_')) < 0 || + (error = git_buf_printf(path, "%d", i)) < 0) + return error; + + i++; + } + + if (i == INT_MAX) { + git_buf_truncate(path, path_len); + + giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path); + return GIT_EEXISTS; + } + + return 0; +} + +static int checkout_write_entry( + checkout_data *data, + checkout_conflictdata *conflict, + const git_index_entry *side) +{ + const char *hint_path = NULL, *suffix; + struct stat st; + int error; + + assert (side == conflict->ours || side == conflict->theirs); + + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, side->path) < 0) + return -1; + + if ((conflict->name_collision || conflict->directoryfile) && + (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && + (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { + + if (side == conflict->ours) + suffix = data->opts.our_label ? data->opts.our_label : + "ours"; + else + suffix = data->opts.their_label ? data->opts.their_label : + "theirs"; + + if (checkout_path_suffixed(&data->path, suffix) < 0) + return -1; + + hint_path = side->path; + } + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + return error; + + return checkout_write_content(data, + &side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st); +} + +static int checkout_write_entries( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) + error = checkout_write_entry(data, conflict, conflict->theirs); + + return error; +} + +static int checkout_merge_path( + git_buf *out, + checkout_data *data, + checkout_conflictdata *conflict, + git_merge_file_result *result) +{ + const char *our_label_raw, *their_label_raw, *suffix; + int error = 0; + + if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0) + return error; + + /* Most conflicts simply use the filename in the index */ + if (!conflict->name_collision) + return 0; + + /* Rename 2->1 conflicts need the branch name appended */ + our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; + their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; + suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; + + if ((error = checkout_path_suffixed(out, suffix)) < 0) + return error; + + return 0; +} + +static int checkout_write_merge( + checkout_data *data, + checkout_conflictdata *conflict) +{ + git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT, + path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT; + 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 = GIT_MERGE_FILE_RESULT_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + int error = 0; + + if ((conflict->ancestor && + (error = git_merge_file_input_from_index_entry( + &ancestor, data->repo, conflict->ancestor)) < 0) || + (error = git_merge_file_input_from_index_entry( + &ours, data->repo, conflict->ours)) < 0 || + (error = git_merge_file_input_from_index_entry( + &theirs, data->repo, conflict->theirs)) < 0) + goto done; + + ancestor.label = NULL; + ours.label = data->opts.our_label ? data->opts.our_label : "ours"; + theirs.label = data->opts.their_label ? data->opts.their_label : "theirs"; + + /* If all the paths are identical, decorate the diff3 file with the branch + * names. Otherwise, append branch_name:path. + */ + if (conflict->ours && conflict->theirs && + strcmp(conflict->ours->path, conflict->theirs->path) != 0) { + + if ((error = conflict_entry_name( + &our_label, ours.label, conflict->ours->path)) < 0 || + (error = conflict_entry_name( + &their_label, theirs.label, conflict->theirs->path)) < 0) + goto done; + + ours.label = git_buf_cstr(&our_label); + theirs.label = git_buf_cstr(&their_label); + } + + if ((error = git_merge_files(&result, &ancestor, &ours, &theirs, 0)) < 0) + goto done; + + if (result.path == NULL || result.mode == 0) { + giterr_set(GITERR_CHECKOUT, "Could not merge contents of file"); + error = GIT_EMERGECONFLICT; + goto done; + } + + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0) + goto done; + + if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || + (error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || + (error = git_filebuf_write(&output, result.data, result.len)) < 0 || + (error = git_filebuf_commit(&output)) < 0) + goto done; + +done: + git_buf_free(&our_label); + git_buf_free(&their_label); + + git_merge_file_input_free(&ancestor); + git_merge_file_input_free(&ours); + git_merge_file_input_free(&theirs); + git_merge_file_result_free(&result); + git_buf_free(&path_workdir); + git_buf_free(&path_suffixed); + + return error; +} + +static int checkout_create_conflicts(checkout_data *data) +{ + checkout_conflictdata *conflict; + size_t i; + int error = 0; + + git_vector_foreach(&data->conflicts, i, conflict) { + /* Both deleted: nothing to do */ + if (conflict->ours == NULL && conflict->theirs == NULL) + error = 0; + + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + conflict->ours) + error = checkout_write_entry(data, conflict, conflict->ours); + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + conflict->theirs) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Ignore the other side of name collisions. */ + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + !conflict->ours && conflict->name_collision) + error = 0; + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + !conflict->theirs && conflict->name_collision) + error = 0; + + /* For modify/delete, name collisions and d/f conflicts, write + * the file (potentially with the name mangled. + */ + else if (conflict->ours != NULL && conflict->theirs == NULL) + error = checkout_write_entry(data, conflict, conflict->ours); + else if (conflict->ours == NULL && conflict->theirs != NULL) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Add/add conflicts and rename 1->2 conflicts, write the + * ours/theirs sides (potentially name mangled). + */ + else if (conflict->one_to_two) + error = checkout_write_entries(data, conflict); + + /* If all sides are links, write the ours side */ + else if (S_ISLNK(conflict->ours->mode) && + S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + /* Link/file conflicts, write the file side */ + else if (S_ISLNK(conflict->ours->mode)) + error = checkout_write_entry(data, conflict, conflict->theirs); + else if (S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + + else + error = checkout_write_merge(data, conflict); + + if (error) + break; + + data->completed_steps++; + report_progress(data, + conflict->ours ? conflict->ours->path : + (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); + } + + return error; +} + + static void checkout_data_clear(checkout_data *data) { + checkout_conflictdata *conflict; + size_t i; + if (data->opts_free_baseline) { git_tree_free(data->opts.baseline); data->opts.baseline = NULL; @@ -1139,6 +1771,11 @@ static void checkout_data_clear(checkout_data *data) git_vector_free(&data->removes); git_pool_clear(&data->pool); + git_vector_foreach(&data->conflicts, i, conflict) + git__free(conflict); + + git_vector_free(&data->conflicts); + git__free(data->pfx); data->pfx = NULL; @@ -1151,7 +1788,7 @@ static void checkout_data_clear(checkout_data *data) static int checkout_data_init( checkout_data *data, git_iterator *target, - git_checkout_opts *proposed) + const git_checkout_opts *proposed) { int error = 0; git_repository *repo = git_iterator_owner(target); @@ -1200,10 +1837,20 @@ static int checkout_data_init( } else { /* otherwise, grab and reload the index */ if ((error = git_repository_index(&data->index, data->repo)) < 0 || - (error = git_index_read(data->index)) < 0) + (error = git_index_read(data->index, true)) < 0) goto cleanup; - /* clear the REUC when doing a tree or commit checkout */ + /* cannot checkout if unresolved conflicts exist */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 && + git_index_has_conflicts(data->index)) { + error = GIT_EMERGECONFLICT; + giterr_set(GITERR_CHECKOUT, + "unresolved conflicts exist in the index"); + goto cleanup; + } + + /* clean conflict data when doing a tree or commit checkout */ + git_index_name_clear(data->index); git_index_reuc_clear(data->index); } } @@ -1235,7 +1882,7 @@ static int checkout_data_init( error = checkout_lookup_head_tree(&data->opts.baseline, repo); - if (error == GIT_EORPHANEDHEAD) { + if (error == GIT_EUNBORNBRANCH) { error = 0; giterr_clear(); } @@ -1245,6 +1892,7 @@ static int checkout_data_init( } if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || + (error = git_vector_init(&data->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) @@ -1261,7 +1909,7 @@ cleanup: int git_checkout_iterator( git_iterator *target, - git_checkout_opts *opts) + const git_checkout_opts *opts) { int error = 0; git_iterator *baseline = NULL, *workdir = NULL; @@ -1315,14 +1963,16 @@ int git_checkout_iterator( goto cleanup; /* Loop through diff (and working directory iterator) building a list of - * actions to be taken, plus look for conflicts and send notifications. + * actions to be taken, plus look for conflicts and send notifications, + * then loop through conflicts. */ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0) goto cleanup; data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + counts[CHECKOUT_ACTION__UPDATE_BLOB] + - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; report_progress(&data, NULL); /* establish 0 baseline */ @@ -1341,6 +1991,10 @@ int git_checkout_iterator( (error = checkout_create_submodules(actions, &data)) < 0) goto cleanup; + if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && + (error = checkout_create_conflicts(&data)) < 0) + goto cleanup; + assert(data.completed_steps == data.total_steps); cleanup: @@ -1351,7 +2005,7 @@ cleanup: (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) error = git_index_write(data.index); - git_diff_list_free(data.diff); + git_diff_free(data.diff); git_iterator_free(workdir); git_iterator_free(baseline); git__free(actions); @@ -1364,7 +2018,7 @@ cleanup: int git_checkout_index( git_repository *repo, git_index *index, - git_checkout_opts *opts) + const git_checkout_opts *opts) { int error; git_iterator *index_i; @@ -1399,7 +2053,7 @@ int git_checkout_index( int git_checkout_tree( git_repository *repo, const git_object *treeish, - git_checkout_opts *opts) + const git_checkout_opts *opts) { int error; git_tree *tree = NULL; @@ -1419,10 +2073,21 @@ int git_checkout_tree( if (!repo) repo = git_object_owner(treeish); - if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { - giterr_set( - GITERR_CHECKOUT, "Provided object cannot be peeled to a tree"); - return -1; + if (treeish) { + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { + giterr_set( + GITERR_CHECKOUT, "Provided object cannot be peeled to a tree"); + return -1; + } + } + else { + if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) { + if (error != GIT_EUNBORNBRANCH) + giterr_set( + GITERR_CHECKOUT, + "HEAD could not be peeled to a tree and no treeish given"); + return error; + } } if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) @@ -1436,20 +2101,8 @@ int git_checkout_tree( int git_checkout_head( git_repository *repo, - git_checkout_opts *opts) + const git_checkout_opts *opts) { - int error; - git_tree *head = NULL; - git_iterator *head_i = NULL; - assert(repo); - - if (!(error = checkout_lookup_head_tree(&head, repo)) && - !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) - error = git_checkout_iterator(head_i, opts); - - git_iterator_free(head_i); - git_tree_free(head); - - return error; + return git_checkout_tree(repo, NULL, opts); } diff --git a/src/checkout.h b/src/checkout.h index b1dc80c38..6d7186860 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -19,6 +19,6 @@ */ extern int git_checkout_iterator( git_iterator *target, - git_checkout_opts *opts); + const git_checkout_opts *opts); #endif diff --git a/src/clone.c b/src/clone.c index 5b6c6f77d..23aacd478 100644 --- a/src/clone.c +++ b/src/clone.c @@ -176,25 +176,20 @@ static int update_head_to_new_branch( return error; } -static int get_head_callback(git_remote_head *head, void *payload) -{ - git_remote_head **destination = (git_remote_head **)payload; - - /* Save the first entry, and terminate the enumeration */ - *destination = head; - return 1; -} - static int update_head_to_remote(git_repository *repo, git_remote *remote) { int retcode = -1; + size_t refs_len; git_refspec dummy_spec; - git_remote_head *remote_head; + const git_remote_head *remote_head, **refs; struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; + if (git_remote_ls(&refs, &refs_len, remote) < 0) + return -1; + /* Did we just clone an empty repository? */ - if (remote->refs.length == 0) { + if (refs_len == 0) { return setup_tracking_config( repo, "master", @@ -202,12 +197,8 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) GIT_REFS_HEADS_MASTER_FILE); } - /* Get the remote's HEAD. This is always the first ref in remote->refs. */ - remote_head = NULL; - - if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head)) - return -1; - + /* Get the remote's HEAD. This is always the first ref in the list. */ + remote_head = refs[0]; assert(remote_head); git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); @@ -220,7 +211,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) memset(&dummy_spec, 0, sizeof(git_refspec)); head_info.refspec = &dummy_spec; } - + /* Determine the remote tracking reference name from the local master */ if (git_refspec_transform_r( &remote_master_name, @@ -270,23 +261,23 @@ cleanup: static int update_head_to_branch( git_repository *repo, - const git_clone_options *options) + const char *remote_name, + const char *branch) { int retcode; git_buf remote_branch_name = GIT_BUF_INIT; git_reference* remote_ref = NULL; - assert(options->checkout_branch); + assert(remote_name && branch); if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", - options->remote_name, options->checkout_branch)) < 0 ) + remote_name, branch)) < 0 ) goto cleanup; if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0) goto cleanup; - retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), - options->checkout_branch); + retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch); cleanup: git_reference_free(remote_ref); @@ -306,41 +297,18 @@ static int create_and_configure_origin( { int error; git_remote *origin = NULL; + const char *name; - if ((error = git_remote_create(&origin, repo, options->remote_name, url)) < 0) + name = options->remote_name ? options->remote_name : "origin"; + if ((error = git_remote_create(&origin, repo, name, url)) < 0) goto on_error; - git_remote_set_cred_acquire_cb(origin, options->cred_acquire_cb, - options->cred_acquire_payload); - git_remote_set_autotag(origin, options->remote_autotag); - /* - * Don't write FETCH_HEAD, we'll check out the remote tracking - * branch ourselves based on the server's default. - */ - git_remote_set_update_fetchhead(origin, 0); + if (options->ignore_cert_errors) + git_remote_check_cert(origin, 0); - if (options->remote_callbacks && - (error = git_remote_set_callbacks(origin, options->remote_callbacks)) < 0) + if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0) goto on_error; - if (options->fetch_spec) { - git_remote_clear_refspecs(origin); - if ((error = git_remote_add_fetch(origin, options->fetch_spec)) < 0) - goto on_error; - } - - if (options->push_spec && - (error = git_remote_add_push(origin, options->push_spec)) < 0) - goto on_error; - - if (options->pushurl && - (error = git_remote_set_pushurl(origin, options->pushurl)) < 0) - goto on_error; - - if (options->transport_flags == GIT_TRANSPORTFLAGS_NO_CHECK_CERT) { - git_remote_check_cert(origin, 0); - } - if ((error = git_remote_save(origin)) < 0) goto on_error; @@ -352,59 +320,10 @@ on_error: return error; } - -static int setup_remotes_and_fetch( - git_repository *repo, - const char *url, - const git_clone_options *options) -{ - int retcode = GIT_ERROR; - git_remote *origin = NULL; - - /* Construct an origin remote */ - if ((retcode = create_and_configure_origin(&origin, repo, url, options)) < 0) - goto on_error; - - git_remote_set_update_fetchhead(origin, 0); - - /* If the download_tags value has not been specified, then make sure to - * download tags as well. It is set here because we want to download tags - * on the initial clone, but do not want to persist the value in the - * configuration file. - */ - if (origin->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_AUTO && - ((retcode = git_remote_add_fetch(origin, "refs/tags/*:refs/tags/*")) < 0)) - goto on_error; - - /* Connect and download everything */ - if ((retcode = git_remote_connect(origin, GIT_DIRECTION_FETCH)) < 0) - goto on_error; - - if ((retcode = git_remote_download(origin, options->fetch_progress_cb, - options->fetch_progress_payload)) < 0) - goto on_error; - - /* Create "origin/foo" branches for all remote branches */ - if ((retcode = git_remote_update_tips(origin)) < 0) - goto on_error; - - /* Point HEAD to the requested branch */ - if (options->checkout_branch) - retcode = update_head_to_branch(repo, options); - /* Point HEAD to the same ref as the remote's head */ - else - retcode = update_head_to_remote(repo, origin); - -on_error: - git_remote_free(origin); - return retcode; -} - - static bool should_checkout( git_repository *repo, bool is_bare, - git_checkout_opts *opts) + const git_checkout_opts *opts) { if (is_bare) return false; @@ -415,64 +334,102 @@ static bool should_checkout( if (opts->checkout_strategy == GIT_CHECKOUT_NONE) return false; - return !git_repository_head_orphan(repo); + return !git_repository_head_unborn(repo); } -static void normalize_options(git_clone_options *dst, const git_clone_options *src) +int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch) { - git_clone_options default_options = GIT_CLONE_OPTIONS_INIT; - if (!src) src = &default_options; + int error = 0, old_fetchhead; + git_strarray refspecs; - *dst = *src; + assert(repo && remote); - /* Provide defaults for null pointers */ - if (!dst->remote_name) dst->remote_name = "origin"; + if (!git_repository_is_empty(repo)) { + giterr_set(GITERR_INVALID, "the repository is not empty"); + return -1; + } + + + if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0) + return error; + + if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) + return error; + + old_fetchhead = git_remote_update_fetchhead(remote); + git_remote_set_update_fetchhead(remote, 0); + + if ((error = git_remote_fetch(remote)) < 0) + goto cleanup; + + if (branch) + error = update_head_to_branch(repo, git_remote_name(remote), branch); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + +cleanup: + git_remote_set_update_fetchhead(remote, old_fetchhead); + /* Go back to the original refspecs */ + if (git_remote_set_fetch_refspecs(remote, &refspecs) < 0) { + git_strarray_free(&refspecs); + return -1; + } + + git_strarray_free(&refspecs); + + return error; } int git_clone( git_repository **out, const char *url, const char *local_path, - const git_clone_options *options) + const git_clone_options *_options) { - int retcode = GIT_ERROR; + int error = 0; git_repository *repo = NULL; - git_clone_options normOptions; - int remove_directory_on_failure = 0; + git_remote *origin; + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; assert(out && url && local_path); - normalize_options(&normOptions, options); - GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); + if (_options) + memcpy(&options, _options, sizeof(git_clone_options)); + + GITERR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); /* Only clone to a new directory or an empty directory */ if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) { giterr_set(GITERR_INVALID, "'%s' exists and is not an empty directory", local_path); - return GIT_ERROR; + return GIT_EEXISTS; } - /* Only remove the directory on failure if we create it */ - remove_directory_on_failure = !git_path_exists(local_path); + /* Only remove the root directory on failure if we create it */ + if (git_path_exists(local_path)) + rmdir_flags |= GIT_RMDIR_SKIP_ROOT; - if (!(retcode = git_repository_init(&repo, local_path, normOptions.bare))) { - if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) { - /* Failed to fetch; clean up */ - git_repository_free(repo); + if ((error = git_repository_init(&repo, local_path, options.bare)) < 0) + return error; - if (remove_directory_on_failure) - git_futils_rmdir_r(local_path, NULL, GIT_RMDIR_REMOVE_FILES); - else - git_futils_cleanupdir_r(local_path); + if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { + error = git_clone_into( + repo, origin, &options.checkout_opts, options.checkout_branch); - } else { - *out = repo; - retcode = 0; - } + git_remote_free(origin); } - if (!retcode && should_checkout(repo, normOptions.bare, &normOptions.checkout_opts)) - retcode = git_checkout_head(*out, &normOptions.checkout_opts); + if (error < 0) { + git_repository_free(repo); + repo = NULL; + (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); + } - return retcode; + *out = repo; + return error; } diff --git a/src/commit.c b/src/commit.c index 1ab9b34f7..91b60bbb2 100644 --- a/src/commit.c +++ b/src/commit.c @@ -19,30 +19,19 @@ #include -static void clear_parents(git_commit *commit) -{ - size_t i; - - for (i = 0; i < commit->parent_ids.length; ++i) { - git_oid *parent = git_vector_get(&commit->parent_ids, i); - git__free(parent); - } - - git_vector_clear(&commit->parent_ids); -} - void git_commit__free(void *_commit) { git_commit *commit = _commit; - clear_parents(commit); - git_vector_free(&commit->parent_ids); + git_array_clear(commit->parent_ids); git_signature_free(commit->author); git_signature_free(commit->committer); - git__free(commit->message); + git__free(commit->raw_header); + git__free(commit->raw_message); git__free(commit->message_encoding); + git__free(commit); } @@ -171,12 +160,35 @@ int git_commit_create( int git_commit__parse(void *_commit, git_odb_object *odb_obj) { git_commit *commit = _commit; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); + const char *buffer_start = git_odb_object_data(odb_obj), *buffer; + const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); git_oid parent_id; + uint32_t parent_count = 0; + size_t header_len; - if (git_vector_init(&commit->parent_ids, 4, NULL) < 0) - return -1; + /* find end-of-header (counting parents as we go) */ + for (buffer = buffer_start; buffer < buffer_end; ++buffer) { + if (!strncmp("\n\n", buffer, 2)) { + ++buffer; + break; + } + if (!strncmp("\nparent ", buffer, strlen("\nparent "))) + ++parent_count; + } + + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GITERR_CHECK_ALLOC(commit->raw_header); + + /* point "buffer" to header data */ + buffer = commit->raw_header; + buffer_end = commit->raw_header + header_len; + + if (parent_count < 1) + parent_count = 1; + + git_array_init_to_size(commit->parent_ids, parent_count); + GITERR_CHECK_ARRAY(commit->parent_ids); if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) goto bad_buffer; @@ -186,13 +198,10 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) */ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { - git_oid *new_id = git__malloc(sizeof(git_oid)); + git_oid *new_id = git_array_alloc(commit->parent_ids); GITERR_CHECK_ALLOC(new_id); git_oid_cpy(new_id, &parent_id); - - if (git_vector_insert(&commit->parent_ids, new_id) < 0) - return -1; } commit->author = git__malloc(sizeof(git_signature)); @@ -208,8 +217,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) return -1; - /* Parse add'l header entries until blank line found */ - while (buffer < buffer_end && *buffer != '\n') { + /* Parse add'l header entries */ + while (buffer < buffer_end) { const char *eoln = buffer; while (eoln < buffer_end && *eoln != '\n') ++eoln; @@ -223,18 +232,21 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (eoln < buffer_end && *eoln == '\n') ++eoln; - buffer = eoln; } - /* buffer is now at the end of the header, double-check and move forward into the message */ - if (buffer < buffer_end && *buffer == '\n') - buffer++; + /* point "buffer" to data after header */ + buffer = git_odb_object_data(odb_obj); + buffer_end = buffer + git_odb_object_size(odb_obj); - /* parse commit message */ + buffer += header_len; + if (*buffer == '\n') + ++buffer; + + /* extract commit message */ if (buffer <= buffer_end) { - commit->message = git__strndup(buffer, buffer_end - buffer); - GITERR_CHECK_ALLOC(commit->message); + commit->raw_message = git__strndup(buffer, buffer_end - buffer); + GITERR_CHECK_ALLOC(commit->raw_message); } return 0; @@ -253,13 +265,27 @@ bad_buffer: GIT_COMMIT_GETTER(const git_signature *, author, commit->author) GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) -GIT_COMMIT_GETTER(const char *, message, commit->message) +GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message) GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) +GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) -GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids)) GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id); +const char *git_commit_message(const git_commit *commit) +{ + const char *message = commit->raw_message; + + assert(commit); + + /* trim leading newlines from raw message */ + while (*message && *message == '\n') + ++message; + + return message; +} + int git_commit_tree(git_tree **tree_out, const git_commit *commit) { assert(commit); @@ -271,7 +297,7 @@ const git_oid *git_commit_parent_id( { assert(commit); - return git_vector_get(&commit->parent_ids, n); + return git_array_get(commit->parent_ids, n); } int git_commit_parent( diff --git a/src/commit.h b/src/commit.h index d0981b125..d452e2975 100644 --- a/src/commit.h +++ b/src/commit.h @@ -10,21 +10,22 @@ #include "git2/commit.h" #include "tree.h" #include "repository.h" -#include "vector.h" +#include "array.h" #include struct git_commit { git_object object; - git_vector parent_ids; + git_array_t(git_oid) parent_ids; git_oid tree_id; git_signature *author; git_signature *committer; char *message_encoding; - char *message; + char *raw_message; + char *raw_header; }; void git_commit__free(void *commit); diff --git a/src/commit_list.c b/src/commit_list.c index bd5b5201a..64416e54d 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -36,7 +36,7 @@ git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_ git_commit_list *p; while ((p = *pp) != NULL) { - if (git_commit_list_time_cmp(p->item, item) < 0) + if (git_commit_list_time_cmp(p->item, item) > 0) break; pp = &p->next; diff --git a/src/common.h b/src/common.h index 02d9ce9b6..159d31b2e 100644 --- a/src/common.h +++ b/src/common.h @@ -73,6 +73,30 @@ void giterr_set(int error_class, const char *string, ...); */ int giterr_set_regex(const regex_t *regex, int error_code); +/** + * Gets the system error code for this thread. + */ +GIT_INLINE(int) giterr_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +/** + * Sets the system error code for this thread. + */ +GIT_INLINE(void) giterr_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + /** * Check a versioned structure for validity */ diff --git a/src/config.c b/src/config.c index 068c40260..0d9471383 100644 --- a/src/config.c +++ b/src/config.c @@ -315,30 +315,241 @@ int git_config_refresh(git_config *cfg) * Loop over all the variables */ +typedef struct { + git_config_iterator parent; + git_config_iterator *current; + const git_config *cfg; + regex_t regex; + int has_regex; + size_t i; +} all_iter; + +static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +{ + file_internal *internal; + + for (; i > 0; --i) { + internal = git_vector_get(&cfg->files, i - 1); + if (!internal || !internal->file) + continue; + + *out = i; + return 0; + } + + return -1; +} + +static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + file_internal *internal; + git_config_backend *backend; + size_t i; + int error = 0; + + if (iter->current != NULL && + (error = iter->current->next(entry, iter->current)) == 0) { + return 0; + } + + if (error < 0 && error != GIT_ITEROVER) + return error; + + do { + if (find_next_backend(&i, iter->cfg, iter->i) < 0) + return GIT_ITEROVER; + + internal = git_vector_get(&iter->cfg->files, i - 1); + backend = internal->file; + iter->i = i - 1; + + if (iter->current) + iter->current->free(iter->current); + + iter->current = NULL; + error = backend->iterator(&iter->current, backend); + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; + + error = iter->current->next(entry, iter->current); + /* If this backend is empty, then keep going */ + if (error == GIT_ITEROVER) + continue; + + return error; + + } while(1); + + return GIT_ITEROVER; +} + +static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) +{ + int error; + all_iter *iter = (all_iter *) _iter; + + /* + * We use the "normal" function to grab the next one across + * backends and then apply the regex + */ + while ((error = all_iter_next(entry, _iter)) == 0) { + /* skip non-matching keys if regexp was provided */ + if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0) + continue; + + /* and simply return if we like the entry's name */ + return 0; + } + + return error; +} + +static void all_iter_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + if (iter->current) + iter->current->free(iter->current); + + git__free(iter); +} + +static void all_iter_glob_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + regfree(&iter->regex); + all_iter_free(_iter); +} + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +{ + all_iter *iter; + + iter = git__calloc(1, sizeof(all_iter)); + GITERR_CHECK_ALLOC(iter); + + iter->parent.free = all_iter_free; + iter->parent.next = all_iter_next; + + iter->i = cfg->files.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +{ + all_iter *iter; + int result; + + if (regexp == NULL) + return git_config_iterator_new(out, cfg); + + iter = git__calloc(1, sizeof(all_iter)); + GITERR_CHECK_ALLOC(iter); + + if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(&iter->regex, result); + regfree(&iter->regex); + return -1; + } + + iter->parent.next = all_iter_glob_next; + iter->parent.free = all_iter_glob_free; + iter->i = cfg->files.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + int git_config_foreach( const git_config *cfg, git_config_foreach_cb cb, void *payload) { return git_config_foreach_match(cfg, NULL, cb, payload); } +int git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + int (*fn)(const git_config_entry *, void *), + void *data) +{ + git_config_entry *entry; + git_config_iterator* iter; + regex_t regex; + int result = 0; + + if (regexp != NULL) { + if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(®ex, result); + regfree(®ex); + return -1; + } + } + + if ((result = backend->iterator(&iter, backend)) < 0) { + iter = NULL; + return -1; + } + + while(!(iter->next(&entry, iter) < 0)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && regexec(®ex, entry->name, 0, NULL, 0) != 0) + continue; + + /* abort iterator on non-zero return value */ + if (fn(entry, data)) { + giterr_clear(); + result = GIT_EUSER; + goto cleanup; + } + } + +cleanup: + if (regexp != NULL) + regfree(®ex); + + iter->free(iter); + + return result; +} + int git_config_foreach_match( const git_config *cfg, const char *regexp, git_config_foreach_cb cb, void *payload) { - int ret = 0; - size_t i; - file_internal *internal; - git_config_backend *file; + int error; + git_config_iterator *iter; + git_config_entry *entry; - for (i = 0; i < cfg->files.length && ret == 0; ++i) { - internal = git_vector_get(&cfg->files, i); - file = internal->file; - ret = file->foreach(file, regexp, cb, payload); + if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + return error; + + while ((error = git_config_next(&entry, iter)) == 0) { + if(cb(entry, payload)) { + giterr_clear(); + error = GIT_EUSER; + break; + } } - return ret; + git_config_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; } /************** @@ -528,31 +739,114 @@ int git_config_get_entry(const git_config_entry **out, const git_config *cfg, co return config_error_notfound(name); } -int git_config_get_multivar( +int git_config_get_multivar_foreach( const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb cb, void *payload) { - file_internal *internal; - git_config_backend *file; - int ret = GIT_ENOTFOUND; - size_t i; + int err, found; + git_config_iterator *iter; + git_config_entry *entry; - /* - * This loop runs the "wrong" way 'round because we need to - * look at every value from the most general to most specific - */ - for (i = cfg->files.length; i > 0; --i) { - internal = git_vector_get(&cfg->files, i - 1); - if (!internal || !internal->file) - continue; - file = internal->file; + if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) + return err; - ret = file->get_multivar(file, name, regexp, cb, payload); - if (ret < 0 && ret != GIT_ENOTFOUND) - return ret; + found = 0; + while ((err = iter->next(&entry, iter)) == 0) { + found = 1; + if(cb(entry, payload)) { + iter->free(iter); + return GIT_EUSER; + } } - return (ret == GIT_ENOTFOUND) ? config_error_notfound(name) : 0; + iter->free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (found == 0 && err == 0) + err = config_error_notfound(name); + + return err; +} + +typedef struct { + git_config_iterator parent; + git_config_iterator *iter; + char *name; + regex_t regex; + int have_regex; +} multivar_iter; + +static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + int error = 0; + + while ((error = iter->iter->next(entry, iter->iter)) == 0) { + if (git__strcmp(iter->name, (*entry)->name)) + continue; + + if (!iter->have_regex) + return 0; + + if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0) + return 0; + } + + return error; +} + +void multivar_iter_free(git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + + iter->iter->free(iter->iter); + + git__free(iter->name); + regfree(&iter->regex); + git__free(iter); +} + +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +{ + multivar_iter *iter = NULL; + git_config_iterator *inner = NULL; + int error; + + if ((error = git_config_iterator_new(&inner, cfg)) < 0) + return error; + + iter = git__calloc(1, sizeof(multivar_iter)); + GITERR_CHECK_ALLOC(iter); + + if ((error = git_config__normalize_name(name, &iter->name)) < 0) + goto on_error; + + if (regexp != NULL) { + error = regcomp(&iter->regex, regexp, REG_EXTENDED); + if (error < 0) { + giterr_set_regex(&iter->regex, error); + error = -1; + regfree(&iter->regex); + goto on_error; + } + + iter->have_regex = 1; + } + + iter->iter = inner; + iter->parent.free = multivar_iter_free; + iter->parent.next = multivar_iter_next; + + *out = (git_config_iterator *) iter; + + return 0; + +on_error: + + inner->free(inner); + git__free(iter); + return error; } int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) @@ -568,6 +862,29 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex return file->set_multivar(file, name, regexp, value); } +int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) +{ + git_config_backend *file; + file_internal *internal; + + internal = git_vector_get(&cfg->files, 0); + if (!internal || !internal->file) + return config_error_nofiles(name); + file = internal->file; + + return file->del_multivar(file, name, regexp); +} + +int git_config_next(git_config_entry **entry, git_config_iterator *iter) +{ + return iter->next(entry, iter); +} + +void git_config_iterator_free(git_config_iterator *iter) +{ + iter->free(iter); +} + static int git_config__find_file_to_path( char *out, size_t outlen, int (*find)(git_buf *buf)) { @@ -811,6 +1128,41 @@ fail_parse: return -1; } +/* Take something the user gave us and make it nice for our hash function */ +int git_config__normalize_name(const char *in, char **out) +{ + char *name, *fdot, *ldot; + + assert(in && out); + + name = git__strdup(in); + GITERR_CHECK_ALLOC(name); + + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); + + if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) + goto invalid; + + /* Validate and downcase up to first dot and after last dot */ + if (git_config_file_normalize_section(name, fdot) < 0 || + git_config_file_normalize_section(ldot + 1, NULL) < 0) + goto invalid; + + /* If there is a middle range, make sure it doesn't have newlines */ + while (fdot < ldot) + if (*fdot++ == '\n') + goto invalid; + + *out = name; + return 0; + +invalid: + git__free(name); + giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in); + return GIT_EINVALIDSPEC; +} + struct rename_data { git_config *config; git_buf *name; diff --git a/src/config.h b/src/config.h index c5c11ae14..01e8465cc 100644 --- a/src/config.h +++ b/src/config.h @@ -47,6 +47,9 @@ extern int git_config_rename_section( * @param out the new backend * @param path where the config file is located */ -extern int git_config_file__ondisk(struct git_config_backend **out, const char *path); +extern int git_config_file__ondisk(git_config_backend **out, const char *path); + +extern int git_config__normalize_name(const char *in, char **out); + #endif diff --git a/src/config_cache.c b/src/config_cache.c index 84de3a5ed..6808521a3 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -67,6 +67,7 @@ static struct map_data _cvar_maps[] = { {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, + {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, }; int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) diff --git a/src/config_file.c b/src/config_file.c index dec952115..15c8de49c 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -15,6 +15,7 @@ #include "git2/sys/config.h" #include "git2/types.h" #include "strmap.h" +#include "array.h" #include #include @@ -25,8 +26,18 @@ GIT__USE_STRMAP; typedef struct cvar_t { struct cvar_t *next; git_config_entry *entry; + int included; /* whether this is part of [include] */ } cvar_t; +typedef struct git_config_file_iter { + git_config_iterator parent; + git_strmap_iter iter; + cvar_t* next_var; +} git_config_file_iter; + +/* Max depth for [include] directives */ +#define MAX_INCLUDE_DEPTH 10 + #define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_TAIL(list) ((list)->tail) @@ -65,34 +76,37 @@ typedef struct cvar_t { (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ (iter) = (tmp)) +struct reader { + time_t file_mtime; + size_t file_size; + char *file_path; + git_buf buffer; + char *read_ptr; + int line_number; + int eof; +}; + typedef struct { git_config_backend parent; git_strmap *values; - struct { - git_buf buffer; - char *read_ptr; - int line_number; - int eof; - } reader; + git_array_t(struct reader) readers; char *file_path; - time_t file_mtime; - size_t file_size; git_config_level_t level; } diskfile_backend; -static int config_parse(diskfile_backend *cfg_file, git_config_level_t level); -static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value); +static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth); +static int parse_variable(struct reader *reader, char **var_name, char **var_value); static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); static char *escape_value(const char *ptr); -static void set_parse_error(diskfile_backend *backend, int col, const char *error_str) +static void set_parse_error(struct reader *reader, int col, const char *error_str) { giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)", - error_str, backend->file_path, backend->reader.line_number, col); + error_str, reader->file_path, reader->line_number, col); } static void cvar_free(cvar_t *var) @@ -106,6 +120,18 @@ static void cvar_free(cvar_t *var) git__free(var); } +static int cvar_length(cvar_t *var) +{ + int length = 0; + + while (var) { + length++; + var = var->next; + } + + return length; +} + int git_config_file_normalize_section(char *start, char *end) { char *scan; @@ -118,7 +144,7 @@ int git_config_file_normalize_section(char *start, char *end) if (end && scan >= end) break; if (isalnum(*scan)) - *scan = tolower(*scan); + *scan = (char)tolower(*scan); else if (*scan != '-' || scan == start) return GIT_EINVALIDSPEC; } @@ -129,41 +155,6 @@ int git_config_file_normalize_section(char *start, char *end) return 0; } -/* Take something the user gave us and make it nice for our hash function */ -static int normalize_name(const char *in, char **out) -{ - char *name, *fdot, *ldot; - - assert(in && out); - - name = git__strdup(in); - GITERR_CHECK_ALLOC(name); - - fdot = strchr(name, '.'); - ldot = strrchr(name, '.'); - - if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) - goto invalid; - - /* Validate and downcase up to first dot and after last dot */ - if (git_config_file_normalize_section(name, fdot) < 0 || - git_config_file_normalize_section(ldot + 1, NULL) < 0) - goto invalid; - - /* If there is a middle range, make sure it doesn't have newlines */ - while (fdot < ldot) - if (*fdot++ == '\n') - goto invalid; - - *out = name; - return 0; - -invalid: - git__free(name); - giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in); - return GIT_EINVALIDSPEC; -} - static void free_vars(git_strmap *values) { cvar_t *var = NULL; @@ -184,6 +175,7 @@ static void free_vars(git_strmap *values) static int config_open(git_config_backend *cfg, git_config_level_t level) { int res; + struct reader *reader; diskfile_backend *b = (diskfile_backend *)cfg; b->level = level; @@ -191,32 +183,52 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) b->values = git_strmap_alloc(); GITERR_CHECK_ALLOC(b->values); - git_buf_init(&b->reader.buffer, 0); + git_array_init(b->readers); + reader = git_array_alloc(b->readers); + memset(reader, 0, sizeof(struct reader)); + + reader->file_path = git__strdup(b->file_path); + GITERR_CHECK_ALLOC(reader->file_path); + + git_buf_init(&reader->buffer, 0); res = git_futils_readbuffer_updated( - &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL); + &reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL); /* It's fine if the file doesn't exist */ if (res == GIT_ENOTFOUND) return 0; - if (res < 0 || (res = config_parse(b, level)) < 0) { + if (res < 0 || (res = config_parse(b, reader, level, 0)) < 0) { free_vars(b->values); b->values = NULL; } - git_buf_free(&b->reader.buffer); + reader = git_array_get(b->readers, 0); + git_buf_free(&reader->buffer); return res; } static int config_refresh(git_config_backend *cfg) { - int res, updated = 0; + int res = 0, updated = 0, any_updated = 0; diskfile_backend *b = (diskfile_backend *)cfg; git_strmap *old_values; + struct reader *reader; + uint32_t i; - res = git_futils_readbuffer_updated( - &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated); - if (res < 0 || !updated) + for (i = 0; i < git_array_size(b->readers); i++) { + reader = git_array_get(b->readers, i); + res = git_futils_readbuffer_updated( + &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated); + + if (res < 0) + return (res == GIT_ENOTFOUND) ? 0 : res; + + if (updated) + any_updated = 1; + } + + if (!any_updated) return (res == GIT_ENOTFOUND) ? 0 : res; /* need to reload - store old values and prep for reload */ @@ -224,74 +236,88 @@ static int config_refresh(git_config_backend *cfg) b->values = git_strmap_alloc(); GITERR_CHECK_ALLOC(b->values); - if ((res = config_parse(b, b->level)) < 0) { + if ((res = config_parse(b, reader, b->level, 0)) < 0) { free_vars(b->values); b->values = old_values; } else { free_vars(old_values); } - git_buf_free(&b->reader.buffer); + git_buf_free(&reader->buffer); return res; } static void backend_free(git_config_backend *_backend) { diskfile_backend *backend = (diskfile_backend *)_backend; + uint32_t i; if (backend == NULL) return; + for (i = 0; i < git_array_size(backend->readers); i++) { + struct reader *r = git_array_get(backend->readers, i); + git__free(r->file_path); + } + git_array_clear(backend->readers); + git__free(backend->file_path); free_vars(backend->values); git__free(backend); } -static int file_foreach( - git_config_backend *backend, - const char *regexp, - int (*fn)(const git_config_entry *, void *), - void *data) +static void config_iterator_free( + git_config_iterator* iter) { - diskfile_backend *b = (diskfile_backend *)backend; - cvar_t *var, *next_var; - const char *key; - regex_t regex; - int result = 0; + git__free(iter); +} - if (!b->values) - return 0; +static int config_iterator_next( + git_config_entry **entry, + git_config_iterator *iter) +{ + git_config_file_iter *it = (git_config_file_iter *) iter; + diskfile_backend *b = (diskfile_backend *) it->parent.backend; + int err = 0; + cvar_t * var; - if (regexp != NULL) { - if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { - giterr_set_regex(®ex, result); - regfree(®ex); - return -1; - } + if (it->next_var == NULL) { + err = git_strmap_next((void**) &var, &(it->iter), b->values); + } else { + var = it->next_var; } - git_strmap_foreach(b->values, key, var, - for (; var != NULL; var = next_var) { - next_var = CVAR_LIST_NEXT(var); + if (err < 0) { + it->next_var = NULL; + return err; + } - /* skip non-matching keys if regexp was provided */ - if (regexp && regexec(®ex, key, 0, NULL, 0) != 0) - continue; + *entry = var->entry; + it->next_var = CVAR_LIST_NEXT(var); - /* abort iterator on non-zero return value */ - if (fn(var->entry, data)) { - giterr_clear(); - result = GIT_EUSER; - goto cleanup; - } - } - ); + return 0; +} -cleanup: - if (regexp != NULL) - regfree(®ex); +static int config_iterator_new( + git_config_iterator **iter, + struct git_config_backend* backend) +{ + diskfile_backend *b = (diskfile_backend *)backend; + git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter)); - return result; + GIT_UNUSED(b); + + GITERR_CHECK_ALLOC(it); + + it->parent.backend = backend; + it->iter = git_strmap_begin(b->values); + it->next_var = NULL; + + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + *iter = (git_config_iterator *) it; + + return 0; } static int config_set(git_config_backend *cfg, const char *name, const char *value) @@ -302,7 +328,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val khiter_t pos; int rval, ret; - if ((rval = normalize_name(name, &key)) < 0) + if ((rval = git_config__normalize_name(name, &key)) < 0) return rval; /* @@ -359,10 +385,10 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val GITERR_CHECK_ALLOC(esc_value); } - if (config_write(b, key, NULL, esc_value) < 0) { + if ((ret = config_write(b, key, NULL, esc_value)) < 0) { git__free(esc_value); cvar_free(var); - return -1; + return ret; } git__free(esc_value); @@ -384,82 +410,23 @@ static int config_get(const git_config_backend *cfg, const char *name, const git char *key; khiter_t pos; int error; + cvar_t *var; - if ((error = normalize_name(name, &key)) < 0) + if ((error = git_config__normalize_name(name, &key)) < 0) return error; pos = git_strmap_lookup_index(b->values, key); git__free(key); /* no error message; the config system will write one */ - if (!git_strmap_valid_index(b->values, pos)) - return GIT_ENOTFOUND; - - *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry; - - return 0; -} - -static int config_get_multivar( - git_config_backend *cfg, - const char *name, - const char *regex_str, - int (*fn)(const git_config_entry *, void *), - void *data) -{ - cvar_t *var; - diskfile_backend *b = (diskfile_backend *)cfg; - char *key; - khiter_t pos; - int error; - - if ((error = normalize_name(name, &key)) < 0) - return error; - - pos = git_strmap_lookup_index(b->values, key); - git__free(key); - if (!git_strmap_valid_index(b->values, pos)) return GIT_ENOTFOUND; var = git_strmap_value_at(b->values, pos); + while (var->next) + var = var->next; - if (regex_str != NULL) { - regex_t regex; - int result; - - /* regex matching; build the regex */ - result = regcomp(®ex, regex_str, REG_EXTENDED); - if (result < 0) { - giterr_set_regex(®ex, result); - regfree(®ex); - return -1; - } - - /* and throw the callback only on the variables that - * match the regex */ - do { - if (regexec(®ex, var->entry->value, 0, NULL, 0) == 0) { - /* early termination by the user is not an error; - * just break and return successfully */ - if (fn(var->entry, data) < 0) - break; - } - - var = var->next; - } while (var != NULL); - regfree(®ex); - } else { - /* no regex; go through all the variables */ - do { - /* early termination by the user is not an error; - * just break and return successfully */ - if (fn(var->entry, data) < 0) - break; - - var = var->next; - } while (var != NULL); - } + *out = var->entry; return 0; } @@ -477,7 +444,7 @@ static int config_set_multivar( assert(regexp); - if ((result = normalize_name(name, &key)) < 0) + if ((result = git_config__normalize_name(name, &key)) < 0) return result; pos = git_strmap_lookup_index(b->values, key); @@ -550,7 +517,7 @@ static int config_delete(git_config_backend *cfg, const char *name) int result; khiter_t pos; - if ((result = normalize_name(name, &key)) < 0) + if ((result = git_config__normalize_name(name, &key)) < 0) return result; pos = git_strmap_lookup_index(b->values, key); @@ -576,6 +543,80 @@ static int config_delete(git_config_backend *cfg, const char *name) return result; } +static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + cvar_t *var, *prev = NULL, *new_head = NULL; + cvar_t **to_delete; + int to_delete_idx; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + regex_t preg; + int result; + khiter_t pos; + + if ((result = git_config__normalize_name(name, &key)) < 0) + return result; + + pos = git_strmap_lookup_index(b->values, key); + + if (!git_strmap_valid_index(b->values, pos)) { + giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); + git__free(key); + return GIT_ENOTFOUND; + } + + var = git_strmap_value_at(b->values, pos); + + result = regcomp(&preg, regexp, REG_EXTENDED); + if (result < 0) { + git__free(key); + giterr_set_regex(&preg, result); + regfree(&preg); + return -1; + } + + to_delete = git__calloc(cvar_length(var), sizeof(cvar_t *)); + GITERR_CHECK_ALLOC(to_delete); + to_delete_idx = 0; + + while (var != NULL) { + cvar_t *next = var->next; + + if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) { + // If we are past the head, reattach previous node to next one, + // otherwise set the new head for the strmap. + if (prev != NULL) { + prev->next = next; + } else { + new_head = next; + } + + to_delete[to_delete_idx++] = var; + } else { + prev = var; + } + + var = next; + } + + if (new_head != NULL) { + git_strmap_set_value_at(b->values, pos, new_head); + } else { + git_strmap_delete_at(b->values, pos); + } + + if (to_delete_idx > 0) + result = config_write(b, key, &preg, NULL); + + while (to_delete_idx-- > 0) + cvar_free(to_delete[to_delete_idx]); + + git__free(key); + git__free(to_delete); + regfree(&preg); + return result; +} + int git_config_file__ondisk(git_config_backend **out, const char *path) { diskfile_backend *backend; @@ -590,11 +631,11 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) backend->parent.open = config_open; backend->parent.get = config_get; - backend->parent.get_multivar = config_get_multivar; backend->parent.set = config_set; backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; - backend->parent.foreach = file_foreach; + backend->parent.del_multivar = config_delete_multivar; + backend->parent.iterator = config_iterator_new; backend->parent.refresh = config_refresh; backend->parent.free = backend_free; @@ -603,26 +644,26 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) return 0; } -static int cfg_getchar_raw(diskfile_backend *cfg) +static int reader_getchar_raw(struct reader *reader) { int c; - c = *cfg->reader.read_ptr++; + c = *reader->read_ptr++; /* Win 32 line breaks: if we find a \r\n sequence, return only the \n as a newline */ - if (c == '\r' && *cfg->reader.read_ptr == '\n') { - cfg->reader.read_ptr++; + if (c == '\r' && *reader->read_ptr == '\n') { + reader->read_ptr++; c = '\n'; } if (c == '\n') - cfg->reader.line_number++; + reader->line_number++; if (c == 0) { - cfg->reader.eof = 1; + reader->eof = 1; c = '\n'; } @@ -632,21 +673,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg) #define SKIP_WHITESPACE (1 << 1) #define SKIP_COMMENTS (1 << 2) -static int cfg_getchar(diskfile_backend *cfg_file, int flags) +static int reader_getchar(struct reader *reader, int flags) { const int skip_whitespace = (flags & SKIP_WHITESPACE); const int skip_comments = (flags & SKIP_COMMENTS); int c; - assert(cfg_file->reader.read_ptr); + assert(reader->read_ptr); - do c = cfg_getchar_raw(cfg_file); - while (skip_whitespace && git__isspace(c) && - !cfg_file->reader.eof); + do { + c = reader_getchar_raw(reader); + } while (skip_whitespace && git__isspace(c) && + !reader->eof); if (skip_comments && (c == '#' || c == ';')) { - do c = cfg_getchar_raw(cfg_file); - while (c != '\n'); + do { + c = reader_getchar_raw(reader); + } while (c != '\n'); } return c; @@ -655,23 +698,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags) /* * Read the next char, but don't move the reading pointer. */ -static int cfg_peek(diskfile_backend *cfg, int flags) +static int reader_peek(struct reader *reader, int flags) { void *old_read_ptr; int old_lineno, old_eof; int ret; - assert(cfg->reader.read_ptr); + assert(reader->read_ptr); - old_read_ptr = cfg->reader.read_ptr; - old_lineno = cfg->reader.line_number; - old_eof = cfg->reader.eof; + old_read_ptr = reader->read_ptr; + old_lineno = reader->line_number; + old_eof = reader->eof; - ret = cfg_getchar(cfg, flags); + ret = reader_getchar(reader, flags); - cfg->reader.read_ptr = old_read_ptr; - cfg->reader.line_number = old_lineno; - cfg->reader.eof = old_eof; + reader->read_ptr = old_read_ptr; + reader->line_number = old_lineno; + reader->eof = old_eof; return ret; } @@ -679,13 +722,13 @@ static int cfg_peek(diskfile_backend *cfg, int flags) /* * Read and consume a line, returning it in newly-allocated memory. */ -static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) +static char *reader_readline(struct reader *reader, bool skip_whitespace) { char *line = NULL; char *line_src, *line_end; size_t line_len; - line_src = cfg->reader.read_ptr; + line_src = reader->read_ptr; if (skip_whitespace) { /* Skip empty empty lines */ @@ -714,10 +757,10 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) line_end++; if (*line_end == '\0') - cfg->reader.eof = 1; + reader->eof = 1; - cfg->reader.line_number++; - cfg->reader.read_ptr = line_end; + reader->line_number++; + reader->read_ptr = line_end; return line; } @@ -725,11 +768,11 @@ static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) /* * Consume a line, without storing it anywhere */ -static void cfg_consume_line(diskfile_backend *cfg) +static void reader_consume_line(struct reader *reader) { char *line_start, *line_end; - line_start = cfg->reader.read_ptr; + line_start = reader->read_ptr; line_end = strchr(line_start, '\n'); /* No newline at EOF */ if(line_end == NULL){ @@ -740,10 +783,10 @@ static void cfg_consume_line(diskfile_backend *cfg) line_end++; if (*line_end == '\0') - cfg->reader.eof = 1; + reader->eof = 1; - cfg->reader.line_number++; - cfg->reader.read_ptr = line_end; + reader->line_number++; + reader->read_ptr = line_end; } GIT_INLINE(int) config_keychar(int c) @@ -751,12 +794,11 @@ GIT_INLINE(int) config_keychar(int c) return isalnum(c) || c == '-'; } -static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name) +static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name) { int c, rpos; char *first_quote, *last_quote; git_buf buf = GIT_BUF_INIT; - int quote_marks; /* * base_name is what came before the space. We should be at the * first quotation mark, except for now, line isn't being kept in @@ -767,7 +809,7 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con last_quote = strrchr(line, '"'); if (last_quote - first_quote == 0) { - set_parse_error(cfg, 0, "Missing closing quotation mark in section header"); + set_parse_error(reader, 0, "Missing closing quotation mark in section header"); return -1; } @@ -775,37 +817,30 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con git_buf_printf(&buf, "%s.", base_name); rpos = 0; - quote_marks = 0; line = first_quote; - c = line[rpos++]; + c = line[++rpos]; /* * At the end of each iteration, whatever is stored in c will be * added to the string. In case of error, jump to out */ do { - if (quote_marks == 2) { - set_parse_error(cfg, rpos, "Unexpected text after closing quotes"); - git_buf_free(&buf); - return -1; - } switch (c) { + case 0: + set_parse_error(reader, 0, "Unexpected end-of-line in section header"); + git_buf_free(&buf); + return -1; + case '"': - ++quote_marks; - continue; + goto end_parse; case '\\': - c = line[rpos++]; + c = line[++rpos]; - switch (c) { - case '"': - case '\\': - break; - - default: - set_parse_error(cfg, rpos, "Unsupported escape sequence"); + if (c == 0) { + set_parse_error(reader, rpos, "Unexpected end-of-line in section header"); git_buf_free(&buf); return -1; } @@ -814,29 +849,37 @@ static int parse_section_header_ext(diskfile_backend *cfg, const char *line, con break; } - git_buf_putc(&buf, c); - } while ((c = line[rpos++]) != ']'); + git_buf_putc(&buf, (char)c); + c = line[++rpos]; + } while (line + rpos < last_quote); + +end_parse: + if (line[rpos] != '"' || line[rpos + 1] != ']') { + set_parse_error(reader, rpos, "Unexpected text after closing quotes"); + git_buf_free(&buf); + return -1; + } *section_name = git_buf_detach(&buf); return 0; } -static int parse_section_header(diskfile_backend *cfg, char **section_out) +static int parse_section_header(struct reader *reader, char **section_out) { char *name, *name_end; int name_length, c, pos; int result; char *line; - line = cfg_readline(cfg, true); + line = reader_readline(reader, true); if (line == NULL) return -1; /* find the end of the variable's name */ - name_end = strchr(line, ']'); + name_end = strrchr(line, ']'); if (name_end == NULL) { git__free(line); - set_parse_error(cfg, 0, "Missing ']' in section header"); + set_parse_error(reader, 0, "Missing ']' in section header"); return -1; } @@ -855,14 +898,14 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) do { if (git__isspace(c)){ name[name_length] = '\0'; - result = parse_section_header_ext(cfg, line, name, section_out); + result = parse_section_header_ext(reader, line, name, section_out); git__free(line); git__free(name); return result; } if (!config_keychar(c) && c != '.') { - set_parse_error(cfg, pos, "Unexpected character in header"); + set_parse_error(reader, pos, "Unexpected character in header"); goto fail_parse; } @@ -871,7 +914,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) } while ((c = line[pos++]) != ']'); if (line[pos - 1] != ']') { - set_parse_error(cfg, pos, "Unexpected end of file"); + set_parse_error(reader, pos, "Unexpected end of file"); goto fail_parse; } @@ -888,14 +931,14 @@ fail_parse: return -1; } -static int skip_bom(diskfile_backend *cfg) +static int skip_bom(struct reader *reader) { git_bom_t bom; int bom_offset = git_buf_text_detect_bom(&bom, - &cfg->reader.buffer, cfg->reader.read_ptr - cfg->reader.buffer.ptr); + &reader->buffer, reader->read_ptr - reader->buffer.ptr); if (bom == GIT_BOM_UTF8) - cfg->reader.read_ptr += bom_offset; + reader->read_ptr += bom_offset; /* TODO: reference implementation is pretty stupid with BoM */ @@ -965,7 +1008,16 @@ static int strip_comments(char *line, int in_quotes) return quote_count; } -static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) +static int included_path(git_buf *out, const char *dir, const char *path) +{ + /* From the user's home */ + if (path[0] == '~' && path[1] == '/') + return git_futils_find_global_file(out, &path[1]); + + return git_path_join_unrooted(out, path, dir, NULL); +} + +static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth) { int c; char *current_section = NULL; @@ -975,39 +1027,46 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) git_buf buf = GIT_BUF_INIT; int result = 0; khiter_t pos; + uint32_t reader_idx; + if (depth >= MAX_INCLUDE_DEPTH) { + giterr_set(GITERR_CONFIG, "Maximum config include depth reached"); + return -1; + } + + reader_idx = git_array_size(cfg_file->readers) - 1; /* Initialize the reading position */ - cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; - cfg_file->reader.eof = 0; + reader->read_ptr = reader->buffer.ptr; + reader->eof = 0; /* If the file is empty, there's nothing for us to do */ - if (*cfg_file->reader.read_ptr == '\0') + if (*reader->read_ptr == '\0') return 0; - skip_bom(cfg_file); + skip_bom(reader); - while (result == 0 && !cfg_file->reader.eof) { + while (result == 0 && !reader->eof) { - c = cfg_peek(cfg_file, SKIP_WHITESPACE); + c = reader_peek(reader, SKIP_WHITESPACE); switch (c) { case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */ - cfg_file->reader.eof = 1; + reader->eof = 1; break; case '[': /* section header, new section begins */ git__free(current_section); current_section = NULL; - result = parse_section_header(cfg_file, ¤t_section); + result = parse_section_header(reader, ¤t_section); break; case ';': case '#': - cfg_consume_line(cfg_file); + reader_consume_line(reader); break; default: /* assume variable declaration */ - result = parse_variable(cfg_file, &var_name, &var_value); + result = parse_variable(reader, &var_name, &var_value); if (result < 0) break; @@ -1028,6 +1087,7 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) var->entry->name = git_buf_detach(&buf); var->entry->value = var_value; var->entry->level = level; + var->included = !!depth; /* Add or append the new config option */ pos = git_strmap_lookup_index(cfg_file->values, var->entry->name); @@ -1044,6 +1104,42 @@ static int config_parse(diskfile_backend *cfg_file, git_config_level_t level) existing->next = var; } + if (!git__strcmp(var->entry->name, "include.path")) { + struct reader *r; + git_buf path = GIT_BUF_INIT; + char *dir; + uint32_t index; + + r = git_array_alloc(cfg_file->readers); + /* The reader may have been reallocated */ + reader = git_array_get(cfg_file->readers, reader_idx); + memset(r, 0, sizeof(struct reader)); + if ((result = git_path_dirname_r(&path, reader->file_path)) < 0) + break; + + /* We need to know out index in the array, as the next config_parse call may realloc */ + index = git_array_size(cfg_file->readers) - 1; + dir = git_buf_detach(&path); + result = included_path(&path, dir, var->entry->value); + git__free(dir); + + if (result < 0) + break; + + r->file_path = git_buf_detach(&path); + git_buf_init(&r->buffer, 0); + if ((result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime, + &r->file_size, NULL)) < 0) + break; + + result = config_parse(cfg_file, r, level, depth+1); + r = git_array_get(cfg_file->readers, index); + git_buf_free(&r->buffer); + + if (result < 0) + break; + } + break; } } @@ -1082,6 +1178,24 @@ static int write_section(git_filebuf *file, const char *key) return result; } +static const char *quotes_for_value(const char *value) +{ + const char *ptr; + + if (value[0] == ' ' || value[0] == '\0') + return "\""; + + for (ptr = value; *ptr; ++ptr) { + if (*ptr == ';' || *ptr == '#') + return "\""; + } + + if (ptr[-1] == ' ') + return "\""; + + return ""; +} + /* * This is pretty much the parsing, except we write out anything we don't have */ @@ -1089,38 +1203,44 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p { int result, c; int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0; - const char *pre_end = NULL, *post_start = NULL, *data_start; + const char *pre_end = NULL, *post_start = NULL, *data_start, *write_start; char *current_section = NULL, *section, *name, *ldot; git_filebuf file = GIT_FILEBUF_INIT; + struct reader *reader = git_array_get(cfg->readers, 0); /* We need to read in our own config file */ - result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path); + result = git_futils_readbuffer(&reader->buffer, cfg->file_path); /* Initialise the reading position */ if (result == GIT_ENOTFOUND) { - cfg->reader.read_ptr = NULL; - cfg->reader.eof = 1; + reader->read_ptr = NULL; + reader->eof = 1; data_start = NULL; - git_buf_clear(&cfg->reader.buffer); + git_buf_clear(&reader->buffer); } else if (result == 0) { - cfg->reader.read_ptr = cfg->reader.buffer.ptr; - cfg->reader.eof = 0; - data_start = cfg->reader.read_ptr; + reader->read_ptr = reader->buffer.ptr; + reader->eof = 0; + data_start = reader->read_ptr; } else { return -1; /* OS error when reading the file */ } - /* Lock the file */ - if (git_filebuf_open(&file, cfg->file_path, 0) < 0) - return -1; + write_start = data_start; - skip_bom(cfg); + /* Lock the file */ + if ((result = git_filebuf_open( + &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) { + git_buf_free(&reader->buffer); + return result; + } + + skip_bom(reader); ldot = strrchr(key, '.'); name = ldot + 1; section = git__strndup(key, ldot - key); - while (!cfg->reader.eof) { - c = cfg_peek(cfg, SKIP_WHITESPACE); + while (!reader->eof) { + c = reader_peek(reader, SKIP_WHITESPACE); if (c == '\0') { /* We've arrived at the end of the file */ break; @@ -1133,11 +1253,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p * new section. If we actually want to replace it, the * default case will take care of updating them. */ - pre_end = post_start = cfg->reader.read_ptr; + pre_end = post_start = reader->read_ptr; git__free(current_section); current_section = NULL; - if (parse_section_header(cfg, ¤t_section) < 0) + if (parse_section_header(reader, ¤t_section) < 0) goto rewrite_fail; /* Keep track of when it stops matching */ @@ -1146,7 +1266,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p } else if (c == ';' || c == '#') { - cfg_consume_line(cfg); + reader_consume_line(reader); } else { @@ -1162,15 +1282,15 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p */ if (!section_matches) { if (!last_section_matched) { - cfg_consume_line(cfg); + reader_consume_line(reader); continue; } } else { int has_matched = 0; char *var_name, *var_value; - pre_end = cfg->reader.read_ptr; - if (parse_variable(cfg, &var_name, &var_value) < 0) + pre_end = reader->read_ptr; + if (parse_variable(reader, &var_name, &var_value) < 0) goto rewrite_fail; /* First try to match the name of the variable */ @@ -1189,23 +1309,28 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p if (!has_matched) continue; - post_start = cfg->reader.read_ptr; + post_start = reader->read_ptr; } /* We've found the variable we wanted to change, so * write anything up to it */ - git_filebuf_write(&file, data_start, pre_end - data_start); + git_filebuf_write(&file, write_start, pre_end - write_start); preg_replaced = 1; /* Then replace the variable. If the value is NULL, it * means we want to delete it, so don't write anything. */ if (value != NULL) { - git_filebuf_printf(&file, "\t%s = %s\n", name, value); + const char *q = quotes_for_value(value); + git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q); } - /* multiline variable? we need to keep reading lines to match */ - if (preg != NULL) { - data_start = post_start; + /* + * If we have a multivar, we should keep looking for entries, + * but only if we're in the right section. Otherwise we'll end up + * looping on the edge of a matching and a non-matching section. + */ + if (section_matches && preg != NULL) { + write_start = post_start; continue; } @@ -1232,12 +1357,14 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p */ if (write_trailer) { /* Write out rest of the file */ - git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start)); + git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start)); } else { if (preg_replaced) { - git_filebuf_printf(&file, "\n%s", data_start); + git_filebuf_printf(&file, "\n%s", write_start); } else { - git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); + const char *q; + + git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size); /* And now if we just need to add a variable */ if (!section_matches && write_section(&file, section) < 0) @@ -1253,10 +1380,11 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p } /* If we are here, there is at least a section line */ - if (cfg->reader.buffer.size > 0 && *(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n') + if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n') git_filebuf_write(&file, "\n", 1); - git_filebuf_printf(&file, "\t%s = %s\n", name, value); + q = quotes_for_value(value); + git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q); } } @@ -1264,10 +1392,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p git__free(current_section); /* refresh stats - if this errors, then commit will error too */ - (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file); + (void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file); - result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); - git_buf_free(&cfg->reader.buffer); + result = git_filebuf_commit(&file); + git_buf_free(&reader->buffer); return result; @@ -1276,7 +1404,7 @@ rewrite_fail: git__free(current_section); git_filebuf_cleanup(&file); - git_buf_free(&cfg->reader.buffer); + git_buf_free(&reader->buffer); return -1; } @@ -1293,6 +1421,9 @@ static char *escape_value(const char *ptr) assert(ptr); len = strlen(ptr); + if (!len) + return git__calloc(1, sizeof(char)); + git_buf_grow(&buf, len); while (*ptr != '\0') { @@ -1365,19 +1496,19 @@ static int is_multiline_var(const char *str) return (end > str) && (count & 1); } -static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes) +static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes) { char *line = NULL, *proc_line = NULL; int quote_count; /* Check that the next line exists */ - line = cfg_readline(cfg, false); + line = reader_readline(reader, false); if (line == NULL) return -1; /* We've reached the end of the file, there is input missing */ if (line[0] == '\0') { - set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var"); + set_parse_error(reader, 0, "Unexpected end of file while parsing multine var"); git__free(line); return -1; } @@ -1387,7 +1518,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i /* If it was just a comment, pretend it didn't exist */ if (line[0] == '\0') { git__free(line); - return parse_multiline_variable(cfg, value, quote_count); + return parse_multiline_variable(reader, value, quote_count); /* TODO: unbounded recursion. This **could** be exploitable */ } @@ -1395,7 +1526,7 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i * standard, this character **has** to be last one in the buf, with * no whitespace after it */ assert(is_multiline_var(value->ptr)); - git_buf_truncate(value, git_buf_len(value) - 1); + git_buf_shorten(value, 1); proc_line = fixup_line(line, in_quotes); if (proc_line == NULL) { @@ -1412,19 +1543,19 @@ static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int i * keep putting stuff in the buffer */ if (is_multiline_var(value->ptr)) - return parse_multiline_variable(cfg, value, quote_count); + return parse_multiline_variable(reader, value, quote_count); return 0; } -static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value) +static int parse_variable(struct reader *reader, char **var_name, char **var_value) { const char *var_end = NULL; const char *value_start = NULL; char *line; int quote_count; - line = cfg_readline(cfg, true); + line = reader_readline(reader, true); if (line == NULL) return -1; @@ -1459,7 +1590,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val GITERR_CHECK_ALLOC(proc_line); git_buf_puts(&multi_value, proc_line); git__free(proc_line); - if (parse_multiline_variable(cfg, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) { + if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) { git__free(*var_name); git__free(line); git_buf_free(&multi_value); diff --git a/src/config_file.h b/src/config_file.h index 7445859c4..d4a1a4061 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -42,7 +42,7 @@ GIT_INLINE(int) git_config_file_foreach( int (*fn)(const git_config_entry *entry, void *data), void *data) { - return cfg->foreach(cfg, NULL, fn, data); + return git_config_backend_foreach_match(cfg, NULL, fn, data); } GIT_INLINE(int) git_config_file_foreach_match( @@ -51,7 +51,7 @@ GIT_INLINE(int) git_config_file_foreach_match( int (*fn)(const git_config_entry *entry, void *data), void *data) { - return cfg->foreach(cfg, regexp, fn, data); + return git_config_backend_foreach_match(cfg, regexp, fn, data); } extern int git_config_file_normalize_section(char *start, char *end); diff --git a/src/crlf.c b/src/crlf.c index 65039f9cc..b25c02cce 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -8,6 +8,7 @@ #include "git2/attr.h" #include "git2/blob.h" #include "git2/index.h" +#include "git2/sys/filter.h" #include "common.h" #include "fileops.h" @@ -19,13 +20,11 @@ struct crlf_attrs { int crlf_action; int eol; + int auto_crlf; }; struct crlf_filter { git_filter f; - struct crlf_attrs attrs; - git_repository *repo; - char path[GIT_FLEX_ARRAY]; }; static int check_crlf(const char *value) @@ -76,41 +75,10 @@ static int crlf_input_action(struct crlf_attrs *ca) return ca->crlf_action; } -static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path) +static int has_cr_in_index(const git_filter_source *src) { -#define NUM_CONV_ATTRS 3 - - static const char *attr_names[NUM_CONV_ATTRS] = { - "crlf", "eol", "text", - }; - - const char *attr_vals[NUM_CONV_ATTRS]; - int error; - - error = git_attr_get_many(attr_vals, - repo, 0, path, NUM_CONV_ATTRS, attr_names); - - if (error == GIT_ENOTFOUND) { - ca->crlf_action = GIT_CRLF_GUESS; - ca->eol = GIT_EOL_UNSET; - return 0; - } - - if (error == 0) { - ca->crlf_action = check_crlf(attr_vals[2]); /* text */ - if (ca->crlf_action == GIT_CRLF_GUESS) - ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */ - - ca->eol = check_eol(attr_vals[1]); /* eol */ - return 0; - } - - return -1; -} - -static int has_cr_in_index(git_filter *self) -{ - struct crlf_filter *filter = (struct crlf_filter *)self; + git_repository *repo = git_filter_source_repo(src); + const char *path = git_filter_source_path(src); git_index *index; const git_index_entry *entry; git_blob *blob; @@ -118,19 +86,22 @@ static int has_cr_in_index(git_filter *self) git_off_t blobsize; bool found_cr; - if (git_repository_index__weakptr(&index, filter->repo) < 0) { + if (!path) + return false; + + if (git_repository_index__weakptr(&index, repo) < 0) { giterr_clear(); return false; } - if (!(entry = git_index_get_bypath(index, filter->path, 0)) && - !(entry = git_index_get_bypath(index, filter->path, 1))) + if (!(entry = git_index_get_bypath(index, path, 0)) && + !(entry = git_index_get_bypath(index, path, 1))) return false; if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ return true; - if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0) + if (git_blob_lookup(&blob, repo, &entry->oid) < 0) return false; blobcontent = git_blob_rawcontent(blob); @@ -147,27 +118,24 @@ static int has_cr_in_index(git_filter *self) } static int crlf_apply_to_odb( - git_filter *self, git_buf *dest, const git_buf *source) + struct crlf_attrs *ca, + git_buf *to, + const git_buf *from, + const git_filter_source *src) { - struct crlf_filter *filter = (struct crlf_filter *)self; - - assert(self && dest && source); - /* Empty file? Nothing to do */ - if (git_buf_len(source) == 0) + if (!git_buf_len(from)) return 0; /* Heuristics to see if we can skip the conversion. * Straight from Core Git. */ - if (filter->attrs.crlf_action == GIT_CRLF_AUTO || - filter->attrs.crlf_action == GIT_CRLF_GUESS) { - + if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) { git_buf_text_stats stats; - /* Check heuristics for binary vs text... */ - if (git_buf_text_gather_stats(&stats, source, false)) - return -1; + /* Check heuristics for binary vs text - returns true if binary */ + if (git_buf_text_gather_stats(&stats, from, false)) + return GIT_PASSTHROUGH; /* * We're currently not going to even try to convert stuff @@ -175,28 +143,28 @@ static int crlf_apply_to_odb( * stuff? */ if (stats.cr != stats.crlf) - return -1; + return GIT_PASSTHROUGH; - if (filter->attrs.crlf_action == GIT_CRLF_GUESS) { + if (ca->crlf_action == GIT_CRLF_GUESS) { /* * If the file in the index has any CR in it, do not convert. * This is the new safer autocrlf handling. */ - if (has_cr_in_index(self)) - return -1; + if (has_cr_in_index(src)) + return GIT_PASSTHROUGH; } if (!stats.cr) - return -1; + return GIT_PASSTHROUGH; } /* Actually drop the carriage returns */ - return git_buf_text_crlf_to_lf(dest, source); + return git_buf_text_crlf_to_lf(to, from); } -static const char *line_ending(struct crlf_filter *filter) +static const char *line_ending(struct crlf_attrs *ca) { - switch (filter->attrs.crlf_action) { + switch (ca->crlf_action) { case GIT_CRLF_BINARY: case GIT_CRLF_INPUT: return "\n"; @@ -213,11 +181,9 @@ static const char *line_ending(struct crlf_filter *filter) goto line_ending_error; } - switch (filter->attrs.eol) { + switch (ca->eol) { case GIT_EOL_UNSET: - return GIT_EOL_NATIVE == GIT_EOL_CRLF - ? "\r\n" - : "\n"; + return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n"; case GIT_EOL_CRLF: return "\r\n"; @@ -235,41 +201,64 @@ line_ending_error: } static int crlf_apply_to_workdir( - git_filter *self, git_buf *dest, const git_buf *source) + struct crlf_attrs *ca, git_buf *to, const git_buf *from) { - struct crlf_filter *filter = (struct crlf_filter *)self; const char *workdir_ending = NULL; - assert(self && dest && source); - /* Empty file? Nothing to do. */ - if (git_buf_len(source) == 0) - return -1; + if (git_buf_len(from) == 0) + return 0; + + /* Don't filter binary files */ + if (git_buf_text_is_binary(from)) + return GIT_PASSTHROUGH; /* Determine proper line ending */ - workdir_ending = line_ending(filter); + workdir_ending = line_ending(ca); if (!workdir_ending) return -1; - if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */ - return -1; - /* for now, only lf->crlf conversion is supported here */ - assert(!strcmp("\r\n", workdir_ending)); - return git_buf_text_lf_to_crlf(dest, source); + if (!strcmp("\n", workdir_ending)) { + if (ca->crlf_action == GIT_CRLF_GUESS && ca->auto_crlf) + return GIT_PASSTHROUGH; + + if (git_buf_find(from, '\r') < 0) + return GIT_PASSTHROUGH; + + if (git_buf_text_crlf_to_lf(to, from) < 0) + return -1; + } else { + /* only other supported option is lf->crlf conversion */ + assert(!strcmp("\r\n", workdir_ending)); + + if (git_buf_text_lf_to_crlf(to, from) < 0) + return -1; + } + + return 0; } -static int find_and_add_filter( - git_vector *filters, git_repository *repo, const char *path, - int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) +static int crlf_check( + git_filter *self, + void **payload, /* points to NULL ptr on entry, may be set */ + const git_filter_source *src, + const char **attr_values) { - struct crlf_attrs ca; - struct crlf_filter *filter; - size_t pathlen; int error; + struct crlf_attrs ca; - /* Load gitattributes for the path */ - if ((error = crlf_load_attributes(&ca, repo, path)) < 0) - return error; + GIT_UNUSED(self); + + if (!attr_values) { + ca.crlf_action = GIT_CRLF_GUESS; + ca.eol = GIT_EOL_UNSET; + } else { + ca.crlf_action = check_crlf(attr_values[2]); /* text */ + if (ca.crlf_action == GIT_CRLF_GUESS) + ca.crlf_action = check_crlf(attr_values[0]); /* clrf */ + ca.eol = check_eol(attr_values[1]); /* eol */ + } + ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT; /* * Use the core Git logic to see if we should perform CRLF for this file @@ -278,41 +267,64 @@ static int find_and_add_filter( ca.crlf_action = crlf_input_action(&ca); if (ca.crlf_action == GIT_CRLF_BINARY) - return 0; + return GIT_PASSTHROUGH; if (ca.crlf_action == GIT_CRLF_GUESS) { - int auto_crlf; - - if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + error = git_repository__cvar( + &ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF); + if (error < 0) return error; - if (auto_crlf == GIT_AUTO_CRLF_FALSE) - return 0; + if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE) + return GIT_PASSTHROUGH; } - /* If we're good, we create a new filter object and push it - * into the filters array */ - pathlen = strlen(path); - filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); - GITERR_CHECK_ALLOC(filter); + *payload = git__malloc(sizeof(ca)); + GITERR_CHECK_ALLOC(*payload); + memcpy(*payload, &ca, sizeof(ca)); - filter->f.apply = apply; - filter->f.do_free = NULL; - memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); - filter->repo = repo; - memcpy(filter->path, path, pathlen + 1); - - return git_vector_insert(filters, filter); + return 0; } -int git_filter_add__crlf_to_odb( - git_vector *filters, git_repository *repo, const char *path) +static int crlf_apply( + git_filter *self, + void **payload, /* may be read and/or set */ + git_buf *to, + const git_buf *from, + const git_filter_source *src) { - return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); + /* initialize payload in case `check` was bypassed */ + if (!*payload) { + int error = crlf_check(self, payload, src, NULL); + if (error < 0 && error != GIT_PASSTHROUGH) + return error; + } + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return crlf_apply_to_workdir(*payload, to, from); + else + return crlf_apply_to_odb(*payload, to, from, src); } -int git_filter_add__crlf_to_workdir( - git_vector *filters, git_repository *repo, const char *path) +static void crlf_cleanup( + git_filter *self, + void *payload) { - return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); + GIT_UNUSED(self); + git__free(payload); +} + +git_filter *git_crlf_filter_new(void) +{ + struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); + + f->f.version = GIT_FILTER_VERSION; + f->f.attributes = "crlf eol text"; + f->f.initialize = NULL; + f->f.shutdown = git_filter_free; + f->f.check = crlf_check; + f->f.apply = crlf_apply; + f->f.cleanup = crlf_cleanup; + + return (git_filter *)f; } diff --git a/src/date.c b/src/date.c index 48841e4f9..7849c2f02 100644 --- a/src/date.c +++ b/src/date.c @@ -823,15 +823,13 @@ static void pending_number(struct tm *tm, int *num) } static git_time_t approxidate_str(const char *date, - const struct timeval *tv, - int *error_ret) + time_t time_sec, + int *error_ret) { int number = 0; int touched = 0; struct tm tm = {0}, now; - time_t time_sec; - time_sec = tv->tv_sec; p_localtime_r(&time_sec, &tm); now = tm; @@ -861,7 +859,7 @@ static git_time_t approxidate_str(const char *date, int git__date_parse(git_time_t *out, const char *date) { - struct timeval tv; + time_t time_sec; git_time_t timestamp; int offset, error_ret=0; @@ -870,7 +868,9 @@ int git__date_parse(git_time_t *out, const char *date) return 0; } - p_gettimeofday(&tv, NULL); - *out = approxidate_str(date, &tv, &error_ret); + if (time(&time_sec) == -1) + return -1; + + *out = approxidate_str(date, time_sec, &error_ret); return error_ret; } diff --git a/src/diff.c b/src/diff.c index 26e117402..4c33a0213 100644 --- a/src/diff.c +++ b/src/diff.c @@ -13,6 +13,7 @@ #include "pathspec.h" #include "index.h" #include "odb.h" +#include "submodule.h" #define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0) #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) @@ -20,7 +21,7 @@ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL)) static git_diff_delta *diff_delta__alloc( - git_diff_list *diff, + git_diff *diff, git_delta_t status, const char *path) { @@ -49,7 +50,7 @@ static git_diff_delta *diff_delta__alloc( } static int diff_notify( - const git_diff_list *diff, + const git_diff *diff, const git_diff_delta *delta, const char *matched_pathspec) { @@ -61,14 +62,17 @@ static int diff_notify( } static int diff_delta__from_one( - git_diff_list *diff, - git_delta_t status, + git_diff *diff, + git_delta_t status, const git_index_entry *entry) { git_diff_delta *delta; const char *matched_pathspec; int notify_res; + if ((entry->flags & GIT_IDXENTRY_VALID) != 0) + return 0; + if (status == GIT_DELTA_IGNORED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) return 0; @@ -77,15 +81,11 @@ static int diff_delta__from_one( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) return 0; - if (entry->mode == GIT_FILEMODE_COMMIT && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) - return 0; - - if (!git_pathspec_match_path( + 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_DELTAS_ARE_ICASE), - &matched_pathspec)) + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + &matched_pathspec, NULL)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -93,6 +93,7 @@ static int diff_delta__from_one( /* This fn is just for single-sided diffs */ assert(status != GIT_DELTA_MODIFIED); + delta->nfiles = 1; if (delta->status == GIT_DELTA_DELETED) { delta->old_file.mode = entry->mode; @@ -123,7 +124,7 @@ static int diff_delta__from_one( } static int diff_delta__from_two( - git_diff_list *diff, + git_diff *diff, git_delta_t status, const git_index_entry *old_entry, uint32_t old_mode, @@ -140,11 +141,6 @@ static int diff_delta__from_two( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) return 0; - if (old_entry->mode == GIT_FILEMODE_COMMIT && - new_entry->mode == GIT_FILEMODE_COMMIT && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) - return 0; - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { uint32_t temp_mode = old_mode; const git_index_entry *temp_entry = old_entry; @@ -156,6 +152,7 @@ static int diff_delta__from_two( delta = diff_delta__alloc(diff, status, canonical_path); GITERR_CHECK_ALLOC(delta); + delta->nfiles = 2; git_oid_cpy(&delta->old_file.oid, &old_entry->oid); delta->old_file.size = old_entry->file_size; @@ -189,7 +186,7 @@ static int diff_delta__from_two( } static git_diff_delta *diff_delta__last_for_item( - git_diff_list *diff, + git_diff *diff, const git_index_entry *item) { git_diff_delta *delta = git_vector_last(&diff->deltas); @@ -247,6 +244,11 @@ GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) return str; } +const char *git_diff_delta__path(const git_diff_delta *delta) +{ + return diff_delta__path(delta); +} + int git_diff_delta__cmp(const void *a, const void *b) { const git_diff_delta *da = a, *db = b; @@ -261,6 +263,26 @@ int git_diff_delta__casecmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } +GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) +{ + return delta->old_file.path ? + delta->old_file.path : delta->new_file.path; +} + +int git_diff_delta__i2w_cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff_delta__i2w_casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta) { @@ -323,13 +345,13 @@ static const char *diff_mnemonic_prefix( return pfx; } -static git_diff_list *diff_list_alloc( +static git_diff *diff_list_alloc( git_repository *repo, git_iterator *old_iter, git_iterator *new_iter) { git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); + git_diff *diff = git__calloc(1, sizeof(git_diff)); if (!diff) return NULL; @@ -343,7 +365,7 @@ static git_diff_list *diff_list_alloc( if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 || git_pool_init(&diff->pool, 1, 0) < 0) { - git_diff_list_free(diff); + git_diff_free(diff); return NULL; } @@ -351,14 +373,14 @@ static git_diff_list *diff_list_alloc( * the ignore_case bit set */ if (!git_iterator_ignore_case(old_iter) && !git_iterator_ignore_case(new_iter)) { - diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; + diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; diff->strcomp = git__strcmp; diff->strncomp = git__strncmp; diff->pfxcomp = git__prefixcmp; diff->entrycomp = git_index_entry__cmp; } else { - diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + diff->opts.flags |= GIT_DIFF_IGNORE_CASE; diff->strcomp = git__strcasecmp; diff->strncomp = git__strncasecmp; @@ -372,7 +394,7 @@ static git_diff_list *diff_list_alloc( } static int diff_list_apply_options( - git_diff_list *diff, + git_diff *diff, const git_diff_options *opts) { git_config *cfg; @@ -382,12 +404,12 @@ static int diff_list_apply_options( if (opts) { /* copy user options (except case sensitivity info from iterators) */ - bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE); + bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); memcpy(&diff->opts, opts, sizeof(diff->opts)); - DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase); + DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); /* initialize pathspec from options */ - if (git_pathspec_init(&diff->pathspec, &opts->pathspec, pool) < 0) + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) return -1; } @@ -396,7 +418,7 @@ static int diff_list_apply_options( diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT)) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* load config values that affect diff behavior */ @@ -407,7 +429,7 @@ static int diff_list_apply_options( diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val) @@ -423,10 +445,28 @@ static int diff_list_apply_options( /* If not given explicit `opts`, check `diff.xyz` configs */ if (!opts) { - diff->opts.context_lines = config_int(cfg, "diff.context", 3); + int context = config_int(cfg, "diff.context", 3); + diff->opts.context_lines = context >= 0 ? (uint16_t)context : 3; - if (config_bool(cfg, "diff.ignoreSubmodules", 0)) - diff->opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; + /* add other defaults here */ + } + + /* Reverse src info if diff is reversed */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + git_iterator_type_t tmp_src = diff->old_src; + diff->old_src = diff->new_src; + diff->new_src = tmp_src; + } + + /* if ignore_submodules not explicitly set, check diff config */ + if (diff->opts.ignore_submodules <= 0) { + const char *str; + + if (git_config_get_string(&str , cfg, "diff.ignoreSubmodules") < 0) + giterr_clear(); + else if (str != NULL && + git_submodule_parse_ignore(&diff->opts.ignore_submodules, str) < 0) + giterr_clear(); } /* if either prefix is not set, figure out appropriate value */ @@ -454,15 +494,15 @@ static int diff_list_apply_options( return -1; if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - const char *swap = diff->opts.old_prefix; - diff->opts.old_prefix = diff->opts.new_prefix; - diff->opts.new_prefix = swap; + const char *tmp_prefix = diff->opts.old_prefix; + diff->opts.old_prefix = diff->opts.new_prefix; + diff->opts.new_prefix = tmp_prefix; } return 0; } -static void diff_list_free(git_diff_list *diff) +static void diff_list_free(git_diff *diff) { git_diff_delta *delta; unsigned int i; @@ -473,14 +513,14 @@ static void diff_list_free(git_diff_list *diff) } git_vector_free(&diff->deltas); - git_pathspec_free(&diff->pathspec); + git_pathspec__vfree(&diff->pathspec); git_pool_clear(&diff->pool); git__memzero(diff, sizeof(*diff)); git__free(diff); } -void git_diff_list_free(git_diff_list *diff) +void git_diff_free(git_diff *diff) { if (!diff) return; @@ -488,7 +528,7 @@ void git_diff_list_free(git_diff_list *diff) GIT_REFCOUNT_DEC(diff, diff_list_free); } -void git_diff_list_addref(git_diff_list *diff) +void git_diff_addref(git_diff *diff) { GIT_REFCOUNT_INC(diff); } @@ -541,21 +581,21 @@ int git_diff__oid_for_file( giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path); result = -1; } else { - git_vector filters = GIT_VECTOR_INIT; + git_filter_list *fl = NULL; - result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB); - if (result >= 0) { + result = git_filter_list_load(&fl, repo, NULL, path, GIT_FILTER_TO_ODB); + if (!result) { int fd = git_futils_open_ro(full_path.ptr); if (fd < 0) result = fd; else { result = git_odb__hashfd_filtered( - oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters); + oid, fd, (size_t)size, GIT_OBJ_BLOB, fl); p_close(fd); } - } - git_filters_free(&filters); + git_filter_list_free(fl); + } } cleanup: @@ -584,45 +624,54 @@ typedef struct { static int maybe_modified_submodule( git_delta_t *status, git_oid *found_oid, - git_diff_list *diff, + git_diff *diff, diff_in_progress *info) { int error = 0; git_submodule *sub; unsigned int sm_status = 0; - const git_oid *sm_oid; + git_submodule_ignore_t ign = diff->opts.ignore_submodules; *status = GIT_DELTA_UNMODIFIED; - if (!DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) && - !(error = git_submodule_lookup( - &sub, diff->repo, info->nitem->path)) && - git_submodule_ignore(sub) != GIT_SUBMODULE_IGNORE_ALL && - !(error = git_submodule_status(&sm_status, sub))) - { - /* check IS_WD_UNMODIFIED because this case is only used - * when the new side of the diff is the working directory - */ - if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) - *status = GIT_DELTA_MODIFIED; + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || + ign == GIT_SUBMODULE_IGNORE_ALL) + return 0; - /* grab OID while we are here */ - if (git_oid_iszero(&info->nitem->oid) && - (sm_oid = git_submodule_wd_id(sub)) != NULL) - git_oid_cpy(found_oid, sm_oid); + if ((error = git_submodule_lookup( + &sub, diff->repo, info->nitem->path)) < 0) { + + /* GIT_EEXISTS means dir with .git in it was found - ignore it */ + if (error == GIT_EEXISTS) { + giterr_clear(); + error = 0; + } + return error; } - /* GIT_EEXISTS means a dir with .git in it was found - ignore it */ - if (error == GIT_EEXISTS) { - giterr_clear(); - error = 0; - } + if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + return 0; - return error; + if ((error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) + return error; + + /* check IS_WD_UNMODIFIED because this case is only used + * when the new side of the diff is the working directory + */ + if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) + *status = GIT_DELTA_MODIFIED; + + /* now that we have a HEAD OID, check if HEAD moved */ + if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->oid, found_oid)) + *status = GIT_DELTA_MODIFIED; + + return 0; } static int maybe_modified( - git_diff_list *diff, + git_diff *diff, diff_in_progress *info) { git_oid noid; @@ -634,11 +683,11 @@ static int maybe_modified( bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); const char *matched_pathspec; - if (!git_pathspec_match_path( + 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_DELTAS_ARE_ICASE), - &matched_pathspec)) + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + &matched_pathspec, NULL)) return 0; memset(&noid, 0, sizeof(noid)); @@ -655,9 +704,8 @@ static int maybe_modified( nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); /* support "assume unchanged" (poorly, b/c we still stat everything) */ - if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) - status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? - GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; + if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) + status = GIT_DELTA_UNMODIFIED; /* support "skip worktree" index bit */ else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) @@ -719,7 +767,7 @@ static int maybe_modified( /* if we got here and decided that the files are modified, but we * haven't calculated the OID of the new item, then calculate it now */ - if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) { + if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) { if (git_oid_iszero(&noid)) { if (git_diff__oid_for_file(diff->repo, nitem->path, nitem->mode, nitem->file_size, &noid) < 0) @@ -741,7 +789,7 @@ static int maybe_modified( } static bool entry_is_prefixed( - git_diff_list *diff, + git_diff *diff, const git_index_entry *item, const git_index_entry *prefix_item) { @@ -758,7 +806,7 @@ static bool entry_is_prefixed( } static int diff_scan_inside_untracked_dir( - git_diff_list *diff, diff_in_progress *info, git_delta_t *delta_type) + git_diff *diff, diff_in_progress *info, git_delta_t *delta_type) { int error = 0; git_buf base = GIT_BUF_INIT; @@ -824,7 +872,7 @@ done: } static int handle_unmatched_new_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = 0; const git_index_entry *nitem = info->nitem; @@ -842,7 +890,7 @@ static int handle_unmatched_new_item( git_buf_clear(&info->ignore_prefix); } - if (S_ISDIR(nitem->mode)) { + if (nitem->mode == GIT_FILEMODE_TREE) { bool recurse_into_dir = contains_oitem; /* if not already inside an ignored dir, check if this is ignored */ @@ -873,7 +921,7 @@ static int handle_unmatched_new_item( */ if (!recurse_into_dir && delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_FAST_UNTRACKED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) { git_diff_delta *last; @@ -946,6 +994,16 @@ static int handle_unmatched_new_item( else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; + else if (nitem->mode == GIT_FILEMODE_COMMIT) { + git_submodule *sm; + + /* ignore things that are not actual submodules */ + if (git_submodule_lookup(&sm, info->repo, nitem->path) != 0) { + giterr_clear(); + delta_type = GIT_DELTA_IGNORED; + } + } + /* Actually create the record for this item if necessary */ if ((error = diff_delta__from_one(diff, delta_type, nitem)) < 0) return error; @@ -969,7 +1027,7 @@ static int handle_unmatched_new_item( } static int handle_unmatched_old_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem); if (error < 0) @@ -1001,7 +1059,7 @@ static int handle_unmatched_old_item( } static int handle_matched_item( - git_diff_list *diff, diff_in_progress *info) + git_diff *diff, diff_in_progress *info) { int error = 0; @@ -1016,7 +1074,7 @@ static int handle_matched_item( } int git_diff__from_iterators( - git_diff_list **diff_ptr, + git_diff **diff_ptr, git_repository *repo, git_iterator *old_iter, git_iterator *new_iter, @@ -1024,7 +1082,7 @@ int git_diff__from_iterators( { int error = 0; diff_in_progress info; - git_diff_list *diff; + git_diff *diff; *diff_ptr = NULL; @@ -1037,7 +1095,7 @@ int git_diff__from_iterators( git_buf_init(&info.ignore_prefix, 0); /* make iterators have matching icase behavior */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || (error = git_iterator_set_ignore_case(new_iter, true)) < 0) goto cleanup; @@ -1085,7 +1143,7 @@ cleanup: if (!error) *diff_ptr = diff; else - git_diff_list_free(diff); + git_diff_free(diff); git_buf_free(&info.ignore_prefix); @@ -1102,7 +1160,7 @@ cleanup: } while (0) int git_diff_tree_to_tree( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_tree *new_tree, @@ -1117,7 +1175,7 @@ int git_diff_tree_to_tree( * currently case insensitive, unless the user explicitly asked * for case insensitivity */ - if (opts && (opts->flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) + if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) iflag = GIT_ITERATOR_IGNORE_CASE; DIFF_FROM_ITERATORS( @@ -1128,8 +1186,19 @@ int git_diff_tree_to_tree( return error; } +static int diff_load_index(git_index **index, git_repository *repo) +{ + int error = git_repository_index__weakptr(index, repo); + + /* reload the repository index when user did not pass one in */ + if (!error && git_index_read(*index, false) < 0) + giterr_clear(); + + return error; +} + int git_diff_tree_to_index( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, @@ -1140,7 +1209,7 @@ int git_diff_tree_to_index( assert(diff && repo); - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + if (!index && (error = diff_load_index(&index, repo)) < 0) return error; if (index->ignore_case) { @@ -1157,9 +1226,9 @@ int git_diff_tree_to_index( git_index__set_ignore_case(index, true); if (!error) { - git_diff_list *d = *diff; + git_diff *d = *diff; - d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + d->opts.flags |= GIT_DIFF_IGNORE_CASE; d->strcomp = git__strcasecmp; d->strncomp = git__strncasecmp; d->pfxcomp = git__prefixcmp_icase; @@ -1174,7 +1243,7 @@ int git_diff_tree_to_index( } int git_diff_index_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts) @@ -1183,7 +1252,7 @@ int git_diff_index_to_workdir( assert(diff && repo); - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + if (!index && (error = diff_load_index(&index, repo)) < 0) return error; DIFF_FROM_ITERATORS( @@ -1195,9 +1264,8 @@ int git_diff_index_to_workdir( return error; } - int git_diff_tree_to_workdir( - git_diff_list **diff, + git_diff **diff, git_repository *repo, git_tree *old_tree, const git_diff_options *opts) @@ -1215,16 +1283,60 @@ int git_diff_tree_to_workdir( return error; } -size_t git_diff_num_deltas(git_diff_list *diff) +int git_diff_tree_to_workdir_with_index( + git_diff **diff, + git_repository *repo, + git_tree *old_tree, + const git_diff_options *opts) { - assert(diff); - return (size_t)diff->deltas.length; + int error = 0; + git_diff *d1 = NULL, *d2 = NULL; + git_index *index = NULL; + + assert(diff && repo); + + if ((error = diff_load_index(&index, repo)) < 0) + return error; + + if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) + error = git_diff_merge(d1, d2); + + git_diff_free(d2); + + if (error) { + git_diff_free(d1); + d1 = NULL; + } + + *diff = d1; + return error; } -size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) +int git_diff_options_init(git_diff_options *options, unsigned int version) +{ + git_diff_options template = GIT_DIFF_OPTIONS_INIT; + + if (version != template.version) { + giterr_set(GITERR_INVALID, + "Invalid version %d for git_diff_options", (int)version); + return -1; + } + + memcpy(options, &template, sizeof(*options)); + return 0; +} + +size_t git_diff_num_deltas(const git_diff *diff) +{ + assert(diff); + return diff->deltas.length; +} + +size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) { size_t i, count = 0; - git_diff_delta *delta; + const git_diff_delta *delta; assert(diff); @@ -1235,9 +1347,20 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) return count; } +const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) +{ + assert(diff); + return git_vector_get(&diff->deltas, idx); +} + +int git_diff_is_sorted_icase(const git_diff *diff) +{ + return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; +} + int git_diff__paired_foreach( - git_diff_list *head2idx, - git_diff_list *idx2wd, + git_diff *head2idx, + git_diff *idx2wd, int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), void *payload) { @@ -1245,10 +1368,12 @@ int git_diff__paired_foreach( git_diff_delta *h2i, *i2w; size_t i, j, i_max, j_max; int (*strcomp)(const char *, const char *) = git__strcmp; - bool icase_mismatch; + bool h2i_icase, i2w_icase, icase_mismatch; i_max = head2idx ? head2idx->deltas.length : 0; j_max = idx2wd ? idx2wd->deltas.length : 0; + if (!i_max && !j_max) + return 0; /* At some point, tree-to-index diffs will probably never ignore case, * even if that isn't true now. Index-to-workdir diffs may or may not @@ -1258,24 +1383,35 @@ int git_diff__paired_foreach( * Therefore the main thing we need to do here is make sure the diffs * are traversed in a compatible order. To do this, we temporarily * resort a mismatched diff to get the order correct. + * + * In order to traverse renames in the index->workdir, we need to + * ensure that we compare the index name on both sides, so we + * always sort by the old name in the i2w list. */ - icase_mismatch = - (head2idx != NULL && idx2wd != NULL && - ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE)); + h2i_icase = head2idx != NULL && + (head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; - /* force case-sensitive delta sort */ - if (icase_mismatch) { - if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); - git_vector_sort(&head2idx->deltas); - } else { - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp); - git_vector_sort(&idx2wd->deltas); - } + i2w_icase = idx2wd != NULL && + (idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; + + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); + + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); + git_vector_sort(&head2idx->deltas); } - else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) + + if (i2w_icase && !icase_mismatch) { strcomp = git__strcasecmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp); + git_vector_sort(&idx2wd->deltas); + } else if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp); + git_vector_sort(&idx2wd->deltas); + } + for (i = 0, j = 0; i < i_max || j < j_max; ) { h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; @@ -1299,14 +1435,16 @@ int git_diff__paired_foreach( } /* restore case-insensitive delta sort */ - if (icase_mismatch) { - if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); - git_vector_sort(&head2idx->deltas); - } else { - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp); - git_vector_sort(&idx2wd->deltas); - } + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); + git_vector_sort(&head2idx->deltas); + } + + /* restore idx2wd sort by new path */ + if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, + i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); + git_vector_sort(&idx2wd->deltas); } return 0; diff --git a/src/diff.h b/src/diff.h index 6ef03ee7c..2c9298a5f 100644 --- a/src/diff.h +++ b/src/diff.h @@ -16,13 +16,14 @@ #include "iterator.h" #include "repository.h" #include "pool.h" +#include "odb.h" #define DIFF_OLD_PREFIX_DEFAULT "a/" #define DIFF_NEW_PREFIX_DEFAULT "b/" enum { GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ - GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ 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? */ @@ -51,7 +52,7 @@ enum { #define GIT_DIFF__VERBOSE (1 << 30) -struct git_diff_list { +struct git_diff { git_refcount rc; git_repository *repo; git_diff_options opts; @@ -71,27 +72,36 @@ struct git_diff_list { extern void git_diff__cleanup_modes( uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); -extern void git_diff_list_addref(git_diff_list *diff); +extern void git_diff_addref(git_diff *diff); extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__casecmp(const void *a, const void *b); +extern const char *git_diff_delta__path(const git_diff_delta *delta); + extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); +extern int git_diff_delta__format_file_header( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen); + extern int git_diff__oid_for_file( git_repository *, const char *, uint16_t, git_off_t, git_oid *); extern int git_diff__from_iterators( - git_diff_list **diff_ptr, + git_diff **diff_ptr, git_repository *repo, git_iterator *old_iter, git_iterator *new_iter, const git_diff_options *opts); extern int git_diff__paired_foreach( - git_diff_list *idx2head, - git_diff_list *wd2idx, + git_diff *idx2head, + git_diff *wd2idx, int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), void *payload); @@ -106,5 +116,33 @@ extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); extern int git_diff_find_similar__calc_similarity( int *score, void *siga, void *sigb, void *payload); +/* + * Sometimes a git_diff_file will have a zero size; this attempts to + * fill in the size without loading the blob if possible. If that is + * not possible, then it will return the git_odb_object that had to be + * loaded and the caller can use it or dispose of it as needed. + */ +GIT_INLINE(int) git_diff_file__resolve_zero_size( + git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) +{ + int error; + git_odb *odb; + size_t len; + git_otype type; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + error = git_odb__read_header_or_object( + odb_obj, &len, &type, odb, &file->oid); + + git_odb_free(odb); + + if (!error) + file->size = (git_off_t)len; + + return error; +} + #endif diff --git a/src/diff_driver.c b/src/diff_driver.c index 469be0d14..bd5a8fbd9 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -187,7 +187,7 @@ static int git_diff_driver_load( git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_put(&name, "xfuncname", strlen("xfuncname")); - if ((error = git_config_get_multivar( + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { if (error != GIT_ENOTFOUND) goto done; @@ -196,7 +196,7 @@ static int git_diff_driver_load( git_buf_truncate(&name, namelen + strlen("diff..")); git_buf_put(&name, "funcname", strlen("funcname")); - if ((error = git_config_get_multivar( + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { if (error != GIT_ENOTFOUND) goto done; @@ -373,10 +373,11 @@ static long diff_context_find( !ctxt->match_line(ctxt->driver, ctxt->line.ptr, ctxt->line.size)) return -1; - git_buf_truncate(&ctxt->line, (size_t)out_size); - git_buf_copy_cstr(out, (size_t)out_size, &ctxt->line); + if (out_size > (long)ctxt->line.size) + out_size = (long)ctxt->line.size; + memcpy(out, ctxt->line.ptr, (size_t)out_size); - return (long)ctxt->line.size; + return out_size; } void git_diff_find_context_init( diff --git a/src/diff_file.c b/src/diff_file.c index 9d06daafa..a4c8641bc 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -88,7 +88,7 @@ static int diff_file_content_init_common( int git_diff_file_content__init_from_diff( git_diff_file_content *fc, - git_diff_list *diff, + git_diff *diff, size_t delta_index, bool use_old) { @@ -110,7 +110,7 @@ int git_diff_file_content__init_from_diff( has_data = use_old; break; case GIT_DELTA_UNTRACKED: has_data = !use_old && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) != 0; + (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; break; case GIT_DELTA_MODIFIED: case GIT_DELTA_COPIED: @@ -241,19 +241,9 @@ static int diff_file_content_load_blob(git_diff_file_content *fc) /* if we don't know size, try to peek at object header first */ if (!fc->file->size) { - git_odb *odb; - size_t len; - git_otype type; - - if (!(error = git_repository_odb__weakptr(&odb, fc->repo))) { - error = git_odb__read_header_or_object( - &odb_obj, &len, &type, odb, &fc->file->oid); - git_odb_free(odb); - } - if (error) + if ((error = git_diff_file__resolve_zero_size( + fc->file, &odb_obj, fc->repo)) < 0) return error; - - fc->file->size = len; } if (diff_file_content_binary_by_size(fc)) @@ -306,9 +296,9 @@ static int diff_file_content_load_workdir_file( git_diff_file_content *fc, git_buf *path) { int error = 0; - git_vector filters = GIT_VECTOR_INIT; - git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + git_filter_list *fl = NULL; git_file fd = git_futils_open_ro(git_buf_cstr(path)); + git_buf raw = GIT_BUF_INIT; if (fd < 0) return fd; @@ -320,41 +310,38 @@ static int diff_file_content_load_workdir_file( if (diff_file_content_binary_by_size(fc)) goto cleanup; - if ((error = git_filters_load( - &filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0) + if ((error = git_filter_list_load( + &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0) goto cleanup; - /* error >= is a filter count */ - if (error == 0) { + /* if there are no filters, try to mmap the file */ + if (fl == NULL) { if (!(error = git_futils_mmap_ro( - &fc->map, fd, 0, (size_t)fc->file->size))) + &fc->map, fd, 0, (size_t)fc->file->size))) { fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; - else /* fall through to try readbuffer below */ - giterr_clear(); - } - - if (error != 0) { - error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size); - if (error < 0) goto cleanup; - - if (!filters.length) - git_buf_swap(&filtered, &raw); - else - error = git_filters_apply(&filtered, &raw, &filters); - - if (!error) { - fc->map.len = git_buf_len(&filtered); - fc->map.data = git_buf_detach(&filtered); - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; } + /* if mmap failed, fall through to try readbuffer below */ + giterr_clear(); + } + + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { + git_buf out = GIT_BUF_INIT; + + error = git_filter_list_apply_to_data(&out, fl, &raw); + git_buf_free(&raw); - git_buf_free(&filtered); + + if (!error) { + fc->map.len = out.size; + fc->map.data = out.ptr; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + } } cleanup: - git_filters_free(&filters); + git_filter_list_free(fl); p_close(fd); return error; @@ -417,6 +404,9 @@ int git_diff_file_content__load(git_diff_file_content *fc) void git_diff_file_content__unload(git_diff_file_content *fc) { + if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) + return; + if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { git__free(fc->map.data); fc->map.data = ""; diff --git a/src/diff_file.h b/src/diff_file.h index fb08cca6a..84bf255aa 100644 --- a/src/diff_file.h +++ b/src/diff_file.h @@ -27,7 +27,7 @@ typedef struct { extern int git_diff_file_content__init_from_diff( git_diff_file_content *fc, - git_diff_list *diff, + git_diff *diff, size_t delta_index, bool use_old); diff --git a/src/diff_patch.c b/src/diff_patch.c index 9060d0a24..cc49d68eb 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -10,38 +10,27 @@ #include "diff_driver.h" #include "diff_patch.h" #include "diff_xdiff.h" - -/* cached information about a single span in a diff */ -typedef struct diff_patch_line diff_patch_line; -struct diff_patch_line { - const char *ptr; - size_t len; - size_t lines, oldno, newno; - char origin; -}; +#include "fileops.h" /* cached information about a hunk in a diff */ typedef struct diff_patch_hunk diff_patch_hunk; struct diff_patch_hunk { - git_diff_range range; - char header[128]; - size_t header_len; + git_diff_hunk hunk; size_t line_start; size_t line_count; }; -struct git_diff_patch { +struct git_patch { git_refcount rc; - git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */ + git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ git_diff_delta *delta; size_t delta_index; git_diff_file_content ofile; git_diff_file_content nfile; uint32_t flags; git_array_t(diff_patch_hunk) hunks; - git_array_t(diff_patch_line) lines; - size_t oldno, newno; - size_t content_size; + git_array_t(git_diff_line) lines; + size_t content_size, context_size, header_size; git_pool flattened; }; @@ -54,12 +43,13 @@ enum { GIT_DIFF_PATCH_FLATTENED = (1 << 5), }; -static void diff_output_init(git_diff_output*, const git_diff_options*, - git_diff_file_cb, git_diff_hunk_cb, git_diff_data_cb, void*); +static void diff_output_init( + git_diff_output*, const git_diff_options*, + git_diff_file_cb, git_diff_hunk_cb, git_diff_line_cb, void*); -static void diff_output_to_patch(git_diff_output *, git_diff_patch *); +static void diff_output_to_patch(git_diff_output *, git_patch *); -static void diff_patch_update_binary(git_diff_patch *patch) +static void diff_patch_update_binary(git_patch *patch) { if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) return; @@ -73,21 +63,21 @@ static void diff_patch_update_binary(git_diff_patch *patch) patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; } -static void diff_patch_init_common(git_diff_patch *patch) +static void diff_patch_init_common(git_patch *patch) { diff_patch_update_binary(patch); if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) - patch->flags |= GIT_DIFF_PATCH_LOADED; /* set LOADED but not DIFFABLE */ + patch->flags |= GIT_DIFF_PATCH_LOADED; /* LOADED but not DIFFABLE */ patch->flags |= GIT_DIFF_PATCH_INITIALIZED; if (patch->diff) - git_diff_list_addref(patch->diff); + git_diff_addref(patch->diff); } static int diff_patch_init_from_diff( - git_diff_patch *patch, git_diff_list *diff, size_t delta_index) + git_patch *patch, git_diff *diff, size_t delta_index) { int error = 0; @@ -108,12 +98,10 @@ static int diff_patch_init_from_diff( } static int diff_patch_alloc_from_diff( - git_diff_patch **out, - git_diff_list *diff, - size_t delta_index) + git_patch **out, git_diff *diff, size_t delta_index) { int error; - git_diff_patch *patch = git__calloc(1, sizeof(git_diff_patch)); + git_patch *patch = git__calloc(1, sizeof(git_patch)); GITERR_CHECK_ALLOC(patch); if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { @@ -128,7 +116,7 @@ static int diff_patch_alloc_from_diff( return error; } -static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) +static int diff_patch_load(git_patch *patch, git_diff_output *output) { int error = 0; bool incomplete_data; @@ -175,9 +163,12 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) goto cleanup; } - /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */ + /* if previously missing an oid, and now that we have it the two sides + * are the same (and not submodules), update MODIFIED -> UNMODIFIED + */ if (incomplete_data && patch->ofile.file->mode == patch->nfile.file->mode && + patch->ofile.file->mode != GIT_FILEMODE_COMMIT && git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) && patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->delta->status = GIT_DELTA_UNMODIFIED; @@ -203,7 +194,7 @@ cleanup: } static int diff_patch_file_callback( - git_diff_patch *patch, git_diff_output *output) + git_patch *patch, git_diff_output *output) { float progress; @@ -219,13 +210,17 @@ static int diff_patch_file_callback( return output->error; } -static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output) +static int diff_patch_generate(git_patch *patch, git_diff_output *output) { int error = 0; if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) return 0; + /* if we are not looking at the hunks and lines, don't do the diff */ + if (!output->hunk_cb && !output->data_cb) + return 0; + if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && (error = diff_patch_load(patch, output)) < 0) return error; @@ -240,7 +235,7 @@ static int diff_patch_generate(git_diff_patch *patch, git_diff_output *output) return error; } -static void diff_patch_free(git_diff_patch *patch) +static void diff_patch_free(git_patch *patch) { git_diff_file_content__clear(&patch->ofile); git_diff_file_content__clear(&patch->nfile); @@ -248,7 +243,7 @@ static void diff_patch_free(git_diff_patch *patch) git_array_clear(patch->lines); git_array_clear(patch->hunks); - git_diff_list_free(patch->diff); /* decrements refcount */ + git_diff_free(patch->diff); /* decrements refcount */ patch->diff = NULL; git_pool_clear(&patch->flattened); @@ -257,7 +252,7 @@ static void diff_patch_free(git_diff_patch *patch) git__free(patch); } -static int diff_required(git_diff_list *diff, const char *action) +static int diff_required(git_diff *diff, const char *action) { if (diff) return 0; @@ -266,22 +261,22 @@ static int diff_required(git_diff_list *diff, const char *action) } int git_diff_foreach( - git_diff_list *diff, + git_diff *diff, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; git_xdiff_output xo; size_t idx; - git_diff_patch patch; + git_patch patch; if (diff_required(diff, "git_diff_foreach") < 0) return -1; - diff_output_init((git_diff_output *)&xo, - &diff->opts, file_cb, hunk_cb, data_cb, payload); + diff_output_init( + &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, &diff->opts); git_vector_foreach(&diff->deltas, idx, patch.delta) { @@ -292,12 +287,12 @@ int git_diff_foreach( if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) { - error = diff_patch_file_callback(&patch, (git_diff_output *)&xo); + error = diff_patch_file_callback(&patch, &xo.output); if (!error) - error = diff_patch_generate(&patch, (git_diff_output *)&xo); + error = diff_patch_generate(&patch, &xo.output); - git_diff_patch_free(&patch); + git_patch_free(&patch); } if (error < 0) @@ -310,7 +305,7 @@ int git_diff_foreach( } typedef struct { - git_diff_patch patch; + git_patch patch; git_diff_delta delta; char paths[GIT_FLEX_ARRAY]; } diff_patch_with_delta; @@ -318,7 +313,7 @@ typedef struct { static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) { int error = 0; - git_diff_patch *patch = &pd->patch; + git_patch *patch = &pd->patch; bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); @@ -422,7 +417,7 @@ int git_diff_blobs( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; @@ -433,7 +428,7 @@ int git_diff_blobs( memset(&xo, 0, sizeof(xo)); diff_output_init( - (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); + &xo.output, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); if (!old_path && new_path) @@ -444,13 +439,13 @@ int git_diff_blobs( error = diff_patch_from_blobs( &pd, &xo, old_blob, old_path, new_blob, new_path, opts); - git_diff_patch_free((git_diff_patch *)&pd); + git_patch_free(&pd.patch); return error; } -int git_diff_patch_from_blobs( - git_diff_patch **out, +int git_patch_from_blobs( + git_patch **out, const git_blob *old_blob, const char *old_path, const git_blob *new_blob, @@ -469,16 +464,16 @@ int git_diff_patch_from_blobs( memset(&xo, 0, sizeof(xo)); - diff_output_to_patch((git_diff_output *)&xo, &pd->patch); + diff_output_to_patch(&xo.output, &pd->patch); git_xdiff_init(&xo, opts); error = diff_patch_from_blobs( pd, &xo, old_blob, old_path, new_blob, new_path, opts); if (!error) - *out = (git_diff_patch *)pd; + *out = (git_patch *)pd; else - git_diff_patch_free((git_diff_patch *)pd); + git_patch_free((git_patch *)pd); return error; } @@ -534,7 +529,7 @@ int git_diff_blob_to_buffer( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { int error = 0; @@ -545,7 +540,7 @@ int git_diff_blob_to_buffer( memset(&xo, 0, sizeof(xo)); diff_output_init( - (git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload); + &xo.output, opts, file_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, opts); if (!old_path && buf_path) @@ -556,13 +551,13 @@ int git_diff_blob_to_buffer( error = diff_patch_from_blob_and_buffer( &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); - git_diff_patch_free((git_diff_patch *)&pd); + git_patch_free(&pd.patch); return error; } -int git_diff_patch_from_blob_and_buffer( - git_diff_patch **out, +int git_patch_from_blob_and_buffer( + git_patch **out, const git_blob *old_blob, const char *old_path, const char *buf, @@ -582,35 +577,31 @@ int git_diff_patch_from_blob_and_buffer( memset(&xo, 0, sizeof(xo)); - diff_output_to_patch((git_diff_output *)&xo, &pd->patch); + diff_output_to_patch(&xo.output, &pd->patch); git_xdiff_init(&xo, opts); error = diff_patch_from_blob_and_buffer( pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); if (!error) - *out = (git_diff_patch *)pd; + *out = (git_patch *)pd; else - git_diff_patch_free((git_diff_patch *)pd); + git_patch_free((git_patch *)pd); return error; } -int git_diff_get_patch( - git_diff_patch **patch_ptr, - const git_diff_delta **delta_ptr, - git_diff_list *diff, - size_t idx) +int git_patch_from_diff( + git_patch **patch_ptr, git_diff *diff, size_t idx) { int error = 0; git_xdiff_output xo; git_diff_delta *delta = NULL; - git_diff_patch *patch = NULL; + git_patch *patch = NULL; if (patch_ptr) *patch_ptr = NULL; - if (delta_ptr) *delta_ptr = NULL; - if (diff_required(diff, "git_diff_get_patch") < 0) + if (diff_required(diff, "git_patch_from_diff") < 0) return -1; delta = git_vector_get(&diff->deltas, idx); @@ -619,9 +610,6 @@ int git_diff_get_patch( return GIT_ENOTFOUND; } - if (delta_ptr) - *delta_ptr = delta; - if (git_diff_delta__should_skip(&diff->opts, delta)) return 0; @@ -634,13 +622,13 @@ int git_diff_get_patch( if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) return error; - diff_output_to_patch((git_diff_output *)&xo, patch); + diff_output_to_patch(&xo.output, patch); git_xdiff_init(&xo, &diff->opts); - error = diff_patch_file_callback(patch, (git_diff_output *)&xo); + error = diff_patch_file_callback(patch, &xo.output); if (!error) - error = diff_patch_generate(patch, (git_diff_output *)&xo); + error = diff_patch_generate(patch, &xo.output); if (!error) { /* if cumulative diff size is < 0.5 total size, flatten the patch */ @@ -648,7 +636,7 @@ int git_diff_get_patch( } if (error || !patch_ptr) - git_diff_patch_free(patch); + git_patch_free(patch); else *patch_ptr = patch; @@ -657,36 +645,36 @@ int git_diff_get_patch( return error; } -void git_diff_patch_free(git_diff_patch *patch) +void git_patch_free(git_patch *patch) { if (patch) GIT_REFCOUNT_DEC(patch, diff_patch_free); } -const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch) +const git_diff_delta *git_patch_get_delta(git_patch *patch) { assert(patch); return patch->delta; } -size_t git_diff_patch_num_hunks(git_diff_patch *patch) +size_t git_patch_num_hunks(git_patch *patch) { assert(patch); return git_array_size(patch->hunks); } -int git_diff_patch_line_stats( +int git_patch_line_stats( size_t *total_ctxt, size_t *total_adds, size_t *total_dels, - const git_diff_patch *patch) + const git_patch *patch) { size_t totals[3], idx; memset(totals, 0, sizeof(totals)); for (idx = 0; idx < git_array_size(patch->lines); ++idx) { - diff_patch_line *line = git_array_get(patch->lines, idx); + git_diff_line *line = git_array_get(patch->lines, idx); if (!line) continue; @@ -718,12 +706,10 @@ static int diff_error_outofrange(const char *thing) return GIT_ENOTFOUND; } -int git_diff_patch_get_hunk( - const git_diff_range **range, - const char **header, - size_t *header_len, +int git_patch_get_hunk( + const git_diff_hunk **out, size_t *lines_in_hunk, - git_diff_patch *patch, + git_patch *patch, size_t hunk_idx) { diff_patch_hunk *hunk; @@ -732,21 +718,17 @@ int git_diff_patch_get_hunk( hunk = git_array_get(patch->hunks, hunk_idx); if (!hunk) { - if (range) *range = NULL; - if (header) *header = NULL; - if (header_len) *header_len = 0; + if (out) *out = NULL; if (lines_in_hunk) *lines_in_hunk = 0; return diff_error_outofrange("hunk"); } - if (range) *range = &hunk->range; - if (header) *header = hunk->header; - if (header_len) *header_len = hunk->header_len; + if (out) *out = &hunk->hunk; if (lines_in_hunk) *lines_in_hunk = hunk->line_count; return 0; } -int git_diff_patch_num_lines_in_hunk(git_diff_patch *patch, size_t hunk_idx) +int git_patch_num_lines_in_hunk(git_patch *patch, size_t hunk_idx) { diff_patch_hunk *hunk; assert(patch); @@ -756,82 +738,96 @@ int git_diff_patch_num_lines_in_hunk(git_diff_patch *patch, size_t hunk_idx) return (int)hunk->line_count; } -int git_diff_patch_get_line_in_hunk( - char *line_origin, - const char **content, - size_t *content_len, - int *old_lineno, - int *new_lineno, - git_diff_patch *patch, +int git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, size_t hunk_idx, size_t line_of_hunk) { diff_patch_hunk *hunk; - diff_patch_line *line; - const char *thing; + git_diff_line *line; assert(patch); if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { - thing = "hunk"; - goto notfound; + if (out) *out = NULL; + return diff_error_outofrange("hunk"); } if (line_of_hunk >= hunk->line_count || !(line = git_array_get( patch->lines, hunk->line_start + line_of_hunk))) { - thing = "line"; - goto notfound; + if (out) *out = NULL; + return diff_error_outofrange("line"); } - if (line_origin) *line_origin = line->origin; - if (content) *content = line->ptr; - if (content_len) *content_len = line->len; - if (old_lineno) *old_lineno = (int)line->oldno; - if (new_lineno) *new_lineno = (int)line->newno; - + if (out) *out = line; return 0; - -notfound: - if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; - if (content) *content = NULL; - if (content_len) *content_len = 0; - if (old_lineno) *old_lineno = -1; - if (new_lineno) *new_lineno = -1; - - return diff_error_outofrange(thing); } -git_diff_list *git_diff_patch__diff(git_diff_patch *patch) +size_t git_patch_size( + git_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers) +{ + size_t out; + + assert(patch); + + out = patch->content_size; + + if (!include_context) + out -= patch->context_size; + + if (include_hunk_headers) + out += patch->header_size; + + if (include_file_headers) { + git_buf file_header = GIT_BUF_INIT; + + if (git_diff_delta__format_file_header( + &file_header, patch->delta, NULL, NULL, 0) < 0) + giterr_clear(); + else + out += git_buf_len(&file_header); + + git_buf_free(&file_header); + } + + return out; +} + +git_diff *git_patch__diff(git_patch *patch) { return patch->diff; } -git_diff_driver *git_diff_patch__driver(git_diff_patch *patch) +git_diff_driver *git_patch__driver(git_patch *patch) { /* ofile driver is representative for whole patch */ return patch->ofile.driver; } -void git_diff_patch__old_data( - char **ptr, size_t *len, git_diff_patch *patch) +void git_patch__old_data( + char **ptr, size_t *len, git_patch *patch) { *ptr = patch->ofile.map.data; *len = patch->ofile.map.len; } -void git_diff_patch__new_data( - char **ptr, size_t *len, git_diff_patch *patch) +void git_patch__new_data( + char **ptr, size_t *len, git_patch *patch) { *ptr = patch->nfile.map.data; *len = patch->nfile.map.len; } -int git_diff_patch__invoke_callbacks( - git_diff_patch *patch, +int git_patch__invoke_callbacks( + git_patch *patch, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *payload) { int error = 0; @@ -846,18 +842,16 @@ int git_diff_patch__invoke_callbacks( for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { diff_patch_hunk *h = git_array_get(patch->hunks, i); - error = hunk_cb( - patch->delta, &h->range, h->header, h->header_len, payload); + error = hunk_cb(patch->delta, &h->hunk, payload); if (!line_cb) continue; for (j = 0; !error && j < h->line_count; ++j) { - diff_patch_line *l = + git_diff_line *l = git_array_get(patch->lines, h->line_start + j); - error = line_cb( - patch->delta, &h->range, l->origin, l->ptr, l->len, payload); + error = line_cb(patch->delta, &h->hunk, l, payload); } } @@ -876,12 +870,10 @@ static int diff_patch_file_cb( static int diff_patch_hunk_cb( const git_diff_delta *delta, - const git_diff_range *range, - const char *header, - size_t header_len, + const git_diff_hunk *hunk_, void *payload) { - git_diff_patch *patch = payload; + git_patch *patch = payload; diff_patch_hunk *hunk; GIT_UNUSED(delta); @@ -889,36 +881,28 @@ static int diff_patch_hunk_cb( hunk = git_array_alloc(patch->hunks); GITERR_CHECK_ALLOC(hunk); - memcpy(&hunk->range, range, sizeof(hunk->range)); + memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); - assert(header_len + 1 < sizeof(hunk->header)); - memcpy(&hunk->header, header, header_len); - hunk->header[header_len] = '\0'; - hunk->header_len = header_len; + patch->header_size += hunk_->header_len; hunk->line_start = git_array_size(patch->lines); hunk->line_count = 0; - patch->oldno = range->old_start; - patch->newno = range->new_start; - return 0; } static int diff_patch_line_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk_, + const git_diff_line *line_, void *payload) { - git_diff_patch *patch = payload; + git_patch *patch = payload; diff_patch_hunk *hunk; - diff_patch_line *line; + git_diff_line *line; GIT_UNUSED(delta); - GIT_UNUSED(range); + GIT_UNUSED(hunk_); hunk = git_array_last(patch->hunks); GITERR_CHECK_ALLOC(hunk); @@ -926,39 +910,20 @@ static int diff_patch_line_cb( line = git_array_alloc(patch->lines); GITERR_CHECK_ALLOC(line); - line->ptr = content; - line->len = content_len; - line->origin = line_origin; - - patch->content_size += content_len; + memcpy(line, line_, sizeof(*line)); /* do some bookkeeping so we can provide old/new line numbers */ - for (line->lines = 0; content_len > 0; --content_len) { - if (*content++ == '\n') - ++line->lines; - } + patch->content_size += line->content_len; - switch (line_origin) { - case GIT_DIFF_LINE_ADDITION: - case GIT_DIFF_LINE_DEL_EOFNL: - line->oldno = -1; - line->newno = patch->newno; - patch->newno += line->lines; - break; - case GIT_DIFF_LINE_DELETION: - case GIT_DIFF_LINE_ADD_EOFNL: - line->oldno = patch->oldno; - line->newno = -1; - patch->oldno += line->lines; - break; - default: - line->oldno = patch->oldno; - line->newno = patch->newno; - patch->oldno += line->lines; - patch->newno += line->lines; - break; - } + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) + patch->content_size += 1; + else if (line->origin == GIT_DIFF_LINE_CONTEXT) { + patch->content_size += 1; + patch->context_size += line->content_len + 1; + } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + patch->context_size += line->content_len; hunk->line_count++; @@ -970,7 +935,7 @@ static void diff_output_init( const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb data_cb, + git_diff_line_cb data_cb, void *payload) { GIT_UNUSED(opts); @@ -983,7 +948,7 @@ static void diff_output_init( out->payload = payload; } -static void diff_output_to_patch(git_diff_output *out, git_diff_patch *patch) +static void diff_output_to_patch(git_diff_output *out, git_patch *patch) { diff_output_init( out, NULL, diff --git a/src/diff_patch.h b/src/diff_patch.h index 56af14600..df2ba4c31 100644 --- a/src/diff_patch.h +++ b/src/diff_patch.h @@ -11,19 +11,20 @@ #include "diff.h" #include "diff_file.h" #include "array.h" +#include "git2/patch.h" -extern git_diff_list *git_diff_patch__diff(git_diff_patch *); +extern git_diff *git_patch__diff(git_patch *); -extern git_diff_driver *git_diff_patch__driver(git_diff_patch *); +extern git_diff_driver *git_patch__driver(git_patch *); -extern void git_diff_patch__old_data(char **, size_t *, git_diff_patch *); -extern void git_diff_patch__new_data(char **, size_t *, git_diff_patch *); +extern void git_patch__old_data(char **, size_t *, git_patch *); +extern void git_patch__new_data(char **, size_t *, git_patch *); -extern int git_diff_patch__invoke_callbacks( - git_diff_patch *patch, +extern int git_patch__invoke_callbacks( + git_patch *patch, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *payload); typedef struct git_diff_output git_diff_output; @@ -31,7 +32,7 @@ struct git_diff_output { /* these callbacks are issued with the diff data */ git_diff_file_cb file_cb; git_diff_hunk_cb hunk_cb; - git_diff_data_cb data_cb; + git_diff_line_cb data_cb; void *payload; /* this records the actual error in cases where it may be obscured */ @@ -40,7 +41,7 @@ struct git_diff_output { /* this callback is used to do the diff and drive the other callbacks. * see diff_xdiff.h for how to use this in practice for now. */ - int (*diff_cb)(git_diff_output *output, git_diff_patch *patch); + int (*diff_cb)(git_diff_output *output, git_patch *patch); }; #endif diff --git a/src/diff_print.c b/src/diff_print.c index 0de548813..b04b11515 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -7,26 +7,39 @@ #include "common.h" #include "diff.h" #include "diff_patch.h" -#include "buffer.h" +#include "fileops.h" typedef struct { - git_diff_list *diff; - git_diff_data_cb print_cb; + git_diff *diff; + git_diff_format_t format; + git_diff_line_cb print_cb; void *payload; git_buf *buf; + uint32_t flags; int oid_strlen; + git_diff_line line; } diff_print_info; static int diff_print_info_init( diff_print_info *pi, - git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload) + git_buf *out, + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) { pi->diff = diff; + pi->format = format; pi->print_cb = cb; pi->payload = payload; pi->buf = out; - if (!diff || !diff->repo) + if (diff) + pi->flags = diff->opts.flags; + + if (diff && diff->opts.oid_abbrev != 0) + pi->oid_strlen = diff->opts.oid_abbrev; + else if (!diff || !diff->repo) pi->oid_strlen = GIT_ABBREV_DEFAULT; else if (git_repository__cvar( &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0) @@ -39,6 +52,11 @@ static int diff_print_info_init( else if (pi->oid_strlen > GIT_OID_HEXSZ + 1) pi->oid_strlen = GIT_OID_HEXSZ + 1; + memset(&pi->line, 0, sizeof(pi->line)); + pi->line.old_lineno = -1; + pi->line.new_lineno = -1; + pi->line.num_lines = 1; + return 0; } @@ -46,7 +64,7 @@ static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; - else if (mode & 0100) /* -V536 */ + else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ /* in git, modes are very regular, so we must have 0100755 mode */ return '*'; else @@ -77,7 +95,35 @@ static int callback_error(void) return GIT_EUSER; } -static int diff_print_one_compact( +static int diff_print_one_name_only( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_buf *out = pi->buf; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && + delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + git_buf_clear(out); + + if (git_buf_puts(out, delta->new_file.path) < 0 || + git_buf_putc(out, '\n')) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) + return callback_error(); + + return 0; +} + +static int diff_print_one_name_status( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; @@ -88,7 +134,7 @@ static int diff_print_one_compact( GIT_UNUSED(progress); - if (code == ' ') + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') return 0; old_suffix = diff_pick_suffix(delta->old_file.mode); @@ -98,12 +144,12 @@ static int diff_print_one_compact( if (delta->old_file.path != delta->new_file.path && strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_buf_printf(out, "%c\t%s%c -> %s%c\n", code, + git_buf_printf(out, "%c\t%s%c %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code, - delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); + git_buf_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (old_suffix != ' ') git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); else @@ -112,31 +158,16 @@ static int diff_print_one_compact( if (git_buf_oom(out)) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(out), git_buf_len(out), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to a print callback in compact format */ -int git_diff_print_compact( - git_diff_list *diff, - git_diff_data_cb print_cb, - void *payload) -{ - int error; - git_buf buf = GIT_BUF_INIT; - diff_print_info pi; - - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi); - - git_buf_free(&buf); - - return error; -} - static int diff_print_one_raw( const git_diff_delta *delta, float progress, void *data) { @@ -147,7 +178,7 @@ static int diff_print_one_raw( GIT_UNUSED(progress); - if (code == ' ') + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') return 0; git_buf_clear(out); @@ -173,38 +204,23 @@ static int diff_print_one_raw( if (git_buf_oom(out)) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(out), git_buf_len(out), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(out); + pi->line.content_len = git_buf_len(out); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to a print callback in raw output format */ -int git_diff_print_raw( - git_diff_list *diff, - git_diff_data_cb print_cb, - void *payload) +static int diff_print_oid_range( + git_buf *out, const git_diff_delta *delta, int oid_strlen) { - int error; - git_buf buf = GIT_BUF_INIT; - diff_print_info pi; - - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi); - - git_buf_free(&buf); - - return error; -} - -static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta) -{ - git_buf *out = pi->buf; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); - git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); + git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid); + git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid); /* TODO: Match git diff more closely */ if (delta->old_file.mode == delta->new_file.mode) { @@ -228,36 +244,15 @@ static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta return 0; } -static int diff_print_patch_file( - const git_diff_delta *delta, float progress, void *data) +static int diff_delta_format_with_paths( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + const char *template) { - diff_print_info *pi = data; - const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL; const char *oldpath = delta->old_file.path; - const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL; const char *newpath = delta->new_file.path; - uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL; - - GIT_UNUSED(progress); - - if (S_ISDIR(delta->new_file.mode) || - delta->status == GIT_DELTA_UNMODIFIED || - delta->status == GIT_DELTA_IGNORED || - (delta->status == GIT_DELTA_UNTRACKED && - (opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)) - return 0; - - if (!oldpfx) - oldpfx = DIFF_OLD_PREFIX_DEFAULT; - if (!newpfx) - newpfx = DIFF_NEW_PREFIX_DEFAULT; - - git_buf_clear(pi->buf); - git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", - oldpfx, delta->old_file.path, newpfx, delta->new_file.path); - - if (diff_print_oid_range(pi, delta) < 0) - return -1; if (git_oid_iszero(&delta->old_file.oid)) { oldpfx = ""; @@ -268,30 +263,83 @@ static int diff_print_patch_file( newpath = "/dev/null"; } - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) { - git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); - git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); - } + return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath); +} - if (git_buf_oom(pi->buf)) +int git_diff_delta__format_file_header( + git_buf *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen) +{ + if (!oldpfx) + oldpfx = DIFF_OLD_PREFIX_DEFAULT; + if (!newpfx) + newpfx = DIFF_NEW_PREFIX_DEFAULT; + if (!oid_strlen) + oid_strlen = GIT_ABBREV_DEFAULT + 1; + + git_buf_clear(out); + + git_buf_printf(out, "diff --git %s%s %s%s\n", + oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + + if (diff_print_oid_range(out, delta, oid_strlen) < 0) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + diff_delta_format_with_paths( + out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n"); + + return git_buf_oom(out) ? -1 : 0; +} + +static int diff_print_patch_file( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + const char *oldpfx = + pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *newpfx = + pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; + + GIT_UNUSED(progress); + + if (S_ISDIR(delta->new_file.mode) || + delta->status == GIT_DELTA_UNMODIFIED || + delta->status == GIT_DELTA_IGNORED || + (delta->status == GIT_DELTA_UNTRACKED && + (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) + return 0; + + if (git_diff_delta__format_file_header( + pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_buf_cstr(pi->buf); + pi->line.content_len = git_buf_len(pi->buf); + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) return 0; git_buf_clear(pi->buf); - git_buf_printf( - pi->buf, "Binary files %s%s and %s%s differ\n", - oldpfx, oldpath, newpfx, newpath); - if (git_buf_oom(pi->buf)) + + if (diff_delta_format_with_paths( + pi->buf, delta, oldpfx, newpfx, + "Binary files %s%s and %s%s differ\n") < 0) return -1; - if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + pi->line.origin = GIT_DIFF_LINE_BINARY; + pi->line.content = git_buf_cstr(pi->buf); + pi->line.content_len = git_buf_len(pi->buf); + pi->line.num_lines = 1; + + if (pi->print_cb(delta, NULL, &pi->line, pi->payload)) return callback_error(); return 0; @@ -299,9 +347,7 @@ static int diff_print_patch_file( static int diff_print_patch_hunk( const git_diff_delta *d, - const git_diff_range *r, - const char *header, - size_t header_len, + const git_diff_hunk *h, void *data) { diff_print_info *pi = data; @@ -309,12 +355,11 @@ static int diff_print_patch_hunk( if (S_ISDIR(d->new_file.mode)) return 0; - git_buf_clear(pi->buf); - if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) - return -1; + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; + pi->line.content = h->header; + pi->line.content_len = h->header_len; - if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + if (pi->print_cb(d, h, &pi->line, pi->payload)) return callback_error(); return 0; @@ -322,10 +367,8 @@ static int diff_print_patch_hunk( static int diff_print_patch_line( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, /* GIT_DIFF_LINE value from above */ - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *data) { diff_print_info *pi = data; @@ -333,49 +376,63 @@ static int diff_print_patch_line( if (S_ISDIR(delta->new_file.mode)) return 0; - git_buf_clear(pi->buf); - - if (line_origin == GIT_DIFF_LINE_ADDITION || - line_origin == GIT_DIFF_LINE_DELETION || - line_origin == GIT_DIFF_LINE_CONTEXT) - git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content); - else if (content_len > 0) - git_buf_printf(pi->buf, "%.*s", (int)content_len, content); - - if (git_buf_oom(pi->buf)) - return -1; - - if (pi->print_cb(delta, range, line_origin, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + if (pi->print_cb(delta, hunk, line, pi->payload)) return callback_error(); return 0; } -/* print a git_diff_list to an output callback in patch format */ -int git_diff_print_patch( - git_diff_list *diff, - git_diff_data_cb print_cb, +/* print a git_diff to an output callback */ +int git_diff_print( + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb print_cb, void *payload) { int error; git_buf buf = GIT_BUF_INIT; diff_print_info pi; + git_diff_file_cb print_file = NULL; + git_diff_hunk_cb print_hunk = NULL; + git_diff_line_cb print_line = NULL; - if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) + switch (format) { + case GIT_DIFF_FORMAT_PATCH: + print_file = diff_print_patch_file; + print_hunk = diff_print_patch_hunk; + print_line = diff_print_patch_line; + break; + case GIT_DIFF_FORMAT_PATCH_HEADER: + print_file = diff_print_patch_file; + break; + case GIT_DIFF_FORMAT_RAW: + print_file = diff_print_one_raw; + break; + case GIT_DIFF_FORMAT_NAME_ONLY: + print_file = diff_print_one_name_only; + break; + case GIT_DIFF_FORMAT_NAME_STATUS: + print_file = diff_print_one_name_status; + break; + default: + giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format); + return -1; + } + + if (!(error = diff_print_info_init( + &pi, &buf, diff, format, print_cb, payload))) error = git_diff_foreach( - diff, diff_print_patch_file, diff_print_patch_hunk, - diff_print_patch_line, &pi); + diff, print_file, print_hunk, print_line, &pi); git_buf_free(&buf); return error; } -/* print a git_diff_patch to an output callback */ -int git_diff_patch_print( - git_diff_patch *patch, - git_diff_data_cb print_cb, +/* print a git_patch to an output callback */ +int git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, void *payload) { int error; @@ -385,8 +442,9 @@ int git_diff_patch_print( assert(patch && print_cb); if (!(error = diff_print_info_init( - &pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) - error = git_diff_patch__invoke_callbacks( + &pi, &temp, git_patch__diff(patch), + GIT_DIFF_FORMAT_PATCH, print_cb, payload))) + error = git_patch__invoke_callbacks( patch, diff_print_patch_file, diff_print_patch_hunk, diff_print_patch_line, &pi); @@ -397,26 +455,30 @@ int git_diff_patch_print( static int diff_print_to_buffer_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *payload) { git_buf *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); - return git_buf_put(output, content, content_len); + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION || + line->origin == GIT_DIFF_LINE_CONTEXT) + git_buf_putc(output, line->origin); + + return git_buf_put(output, line->content, line->content_len); } -/* print a git_diff_patch to a string buffer */ -int git_diff_patch_to_str( +/* print a git_patch to a string buffer */ +int git_patch_to_str( char **string, - git_diff_patch *patch) + git_patch *patch) { int error; git_buf output = GIT_BUF_INIT; - error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output); + error = git_patch_print(patch, diff_print_to_buffer_cb, &output); /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, * meaning a memory allocation failure, so just map to -1... diff --git a/src/diff_tform.c b/src/diff_tform.c index 8c4e96ecf..28a9cc70d 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -46,7 +46,9 @@ fail: } static git_diff_delta *diff_delta__merge_like_cgit( - const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) { git_diff_delta *dup; @@ -96,15 +98,46 @@ static git_diff_delta *diff_delta__merge_like_cgit( return dup; } -int git_diff_merge( - git_diff_list *onto, - const git_diff_list *from) +static git_diff_delta *diff_delta__merge_like_cgit_reversed( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + git_diff_delta *dup; + + /* reversed version of above logic */ + + if (a->status == GIT_DELTA_UNMODIFIED) + return diff_delta__dup(b, pool); + + if ((dup = diff_delta__dup(a, pool)) == NULL) + return NULL; + + if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED) + return dup; + + if (dup->status == GIT_DELTA_DELETED) { + if (b->status == GIT_DELTA_ADDED) + dup->status = GIT_DELTA_UNMODIFIED; + } else { + dup->status = b->status; + } + + git_oid_cpy(&dup->old_file.oid, &b->old_file.oid); + dup->old_file.mode = b->old_file.mode; + dup->old_file.size = b->old_file.size; + dup->old_file.flags = b->old_file.flags; + + return dup; +} + +int git_diff_merge(git_diff *onto, const git_diff *from) { int error = 0; git_pool onto_pool; git_vector onto_new; git_diff_delta *delta; - bool ignore_case = false; + bool ignore_case, reversed; unsigned int i, j; assert(onto && from); @@ -112,26 +145,26 @@ int git_diff_merge( if (!from->deltas.length) return 0; + ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); + reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); + + if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || + reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { + giterr_set(GITERR_INVALID, + "Attempt to merge diffs created with conflicting options"); + return -1; + } + if (git_vector_init( &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || git_pool_init(&onto_pool, 1, 0) < 0) return -1; - if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 || - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) - { - ignore_case = true; - - /* This function currently only supports merging diff lists that - * are sorted identically. */ - assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0); - } - 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); - int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); + int cmp = !f ? -1 : !o ? 1 : + STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); if (cmp < 0) { delta = diff_delta__dup(o, &onto_pool); @@ -140,7 +173,9 @@ int git_diff_merge( delta = diff_delta__dup(f, &onto_pool); j++; } else { - delta = diff_delta__merge_like_cgit(o, f, &onto_pool); + delta = reversed ? + diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) : + diff_delta__merge_like_cgit(o, f, &onto_pool); i++; j++; } @@ -160,7 +195,11 @@ int git_diff_merge( if (!error) { git_vector_swap(&onto->deltas, &onto_new); git_pool_swap(&onto->pool, &onto_pool); - onto->new_src = from->new_src; + + if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) + onto->old_src = from->old_src; + else + onto->new_src = from->new_src; /* prefix strings also come from old pool, so recreate those.*/ onto->opts.old_prefix = @@ -230,9 +269,9 @@ int git_diff_find_similar__calc_similarity( #define DEFAULT_RENAME_LIMIT 200 static int normalize_find_opts( - git_diff_list *diff, + git_diff *diff, git_diff_find_options *opts, - git_diff_find_options *given) + const git_diff_find_options *given) { git_config *cfg = NULL; @@ -328,7 +367,7 @@ static int normalize_find_opts( } static int apply_splits_and_deletes( - git_diff_list *diff, size_t expected_size, bool actually_split) + git_diff *diff, size_t expected_size, bool actually_split) { git_vector onto = GIT_VECTOR_INIT; size_t i; @@ -350,6 +389,7 @@ static int apply_splits_and_deletes( goto on_error; deleted->status = GIT_DELTA_DELETED; + deleted->nfiles = 1; memset(&deleted->new_file, 0, sizeof(deleted->new_file)); deleted->new_file.path = deleted->old_file.path; deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; @@ -361,6 +401,7 @@ static int apply_splits_and_deletes( delta->status = GIT_DELTA_UNTRACKED; else delta->status = GIT_DELTA_ADDED; + delta->nfiles = 1; memset(&delta->old_file, 0, sizeof(delta->old_file)); delta->old_file.path = delta->new_file.path; delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; @@ -402,63 +443,105 @@ on_error: return -1; } -GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx) +GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) { git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); return (idx & 1) ? &delta->new_file : &delta->old_file; } -static int similarity_calc( - git_diff_list *diff, +typedef struct { + size_t idx; + git_iterator_type_t src; + git_repository *repo; + git_diff_file *file; + git_buf data; + git_odb_object *odb_obj; + git_blob *blob; +} similarity_info; + +static int similarity_init( + similarity_info *info, git_diff *diff, size_t file_idx) +{ + info->idx = file_idx; + info->src = (file_idx & 1) ? diff->new_src : diff->old_src; + info->repo = diff->repo; + info->file = similarity_get_file(diff, file_idx); + info->odb_obj = NULL; + info->blob = NULL; + git_buf_init(&info->data, 0); + + if (info->file->size > 0) + return 0; + + return git_diff_file__resolve_zero_size( + info->file, &info->odb_obj, info->repo); +} + +static int similarity_sig( + similarity_info *info, const git_diff_find_options *opts, - size_t file_idx, void **cache) { int error = 0; - git_diff_file *file = similarity_get_file(diff, file_idx); - git_iterator_type_t src = (file_idx & 1) ? diff->new_src : diff->old_src; - - if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */ - git_buf path = GIT_BUF_INIT; - - /* TODO: apply wd-to-odb filters to file data if necessary */ + git_diff_file *file = info->file; + if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_buf_joinpath( - &path, git_repository_workdir(diff->repo), file->path)) < 0) + &info->data, git_repository_workdir(info->repo), file->path)) < 0) return error; /* if path is not a regular file, just skip this item */ - if (git_path_isfile(path.ptr)) - error = opts->metric->file_signature( - &cache[file_idx], file, path.ptr, opts->metric->payload); + if (!git_path_isfile(info->data.ptr)) + return 0; - git_buf_free(&path); - } else { /* compute hashsig from blob buffer */ - git_blob *blob = NULL; - git_off_t blobsize; + /* TODO: apply wd-to-odb filters to file data if necessary */ - /* TODO: add max size threshold a la diff? */ + error = opts->metric->file_signature( + &cache[info->idx], info->file, + info->data.ptr, opts->metric->payload); + } else { + /* if we didn't initially know the size, we might have an odb_obj + * around from earlier, so convert that, otherwise load the blob now + */ + if (info->odb_obj != NULL) + error = git_object__from_odb_object( + (git_object **)&info->blob, info->repo, + info->odb_obj, GIT_OBJ_BLOB); + else + error = git_blob_lookup(&info->blob, info->repo, &file->oid); - if (git_blob_lookup(&blob, diff->repo, &file->oid) < 0) { + if (error < 0) { /* if lookup fails, just skip this item in similarity calc */ giterr_clear(); - return 0; + } else { + size_t sz; + + /* index size may not be actual blob size if filtered */ + if (file->size != git_blob_rawsize(info->blob)) + file->size = git_blob_rawsize(info->blob); + + sz = (size_t)(git__is_sizet(file->size) ? file->size : -1); + + error = opts->metric->buffer_signature( + &cache[info->idx], info->file, + git_blob_rawcontent(info->blob), sz, opts->metric->payload); } - - blobsize = git_blob_rawsize(blob); - if (!git__is_sizet(blobsize)) /* ? what to do ? */ - blobsize = (size_t)-1; - - error = opts->metric->buffer_signature( - &cache[file_idx], file, git_blob_rawcontent(blob), - (size_t)blobsize, opts->metric->payload); - - git_blob_free(blob); } return error; } +static void similarity_unload(similarity_info *info) +{ + if (info->odb_obj) + git_odb_object_free(info->odb_obj); + + if (info->blob) + git_blob_free(info->blob); + else + git_buf_free(&info->data); +} + #define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) /* - score < 0 means files cannot be compared @@ -467,7 +550,7 @@ static int similarity_calc( */ static int similarity_measure( int *score, - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, void **cache, size_t a_idx, @@ -476,6 +559,8 @@ static int similarity_measure( git_diff_file *a_file = similarity_get_file(diff, a_idx); git_diff_file *b_file = similarity_get_file(diff, b_idx); bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); + int error = 0; + similarity_info a_info, b_info; *score = -1; @@ -510,23 +595,48 @@ static int similarity_measure( return 0; } + memset(&a_info, 0, sizeof(a_info)); + memset(&b_info, 0, sizeof(b_info)); + + /* set up similarity data (will try to update missing file sizes) */ + if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) + return error; + if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) + goto cleanup; + + /* check if file sizes are nowhere near each other */ + if (a_file->size > 127 && + b_file->size > 127 && + (a_file->size > (b_file->size << 3) || + b_file->size > (a_file->size << 3))) + goto cleanup; + /* update signature cache if needed */ - if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0) - return -1; - if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0) - return -1; + if (!cache[a_idx]) { + if ((error = similarity_sig(&a_info, opts, cache)) < 0) + goto cleanup; + } + if (!cache[b_idx]) { + if ((error = similarity_sig(&b_info, opts, cache)) < 0) + goto cleanup; + } - /* some metrics may not wish to process this file (too big / too small) */ - if (!cache[a_idx] || !cache[b_idx]) - return 0; + /* calculate similarity provided that the metric choose to process + * both the a and b files (some may not if file is too big, etc). + */ + if (cache[a_idx] && cache[b_idx]) + error = opts->metric->similarity( + score, cache[a_idx], cache[b_idx], opts->metric->payload); - /* compare signatures */ - return opts->metric->similarity( - score, cache[a_idx], cache[b_idx], opts->metric->payload); +cleanup: + similarity_unload(&a_info); + similarity_unload(&b_info); + + return error; } static int calc_self_similarity( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -543,7 +653,7 @@ static int calc_self_similarity( return error; if (similarity >= 0) { - delta->similarity = (uint32_t)similarity; + delta->similarity = (uint16_t)similarity; delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; } @@ -551,7 +661,7 @@ static int calc_self_similarity( } static bool is_rename_target( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -590,11 +700,13 @@ static bool is_rename_target( return false; case GIT_DELTA_UNTRACKED: - case GIT_DELTA_IGNORED: if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) return false; break; + case GIT_DELTA_IGNORED: + return false; + default: /* all other status values should be checked */ break; } @@ -604,7 +716,7 @@ static bool is_rename_target( } static bool is_rename_source( - git_diff_list *diff, + git_diff *diff, const git_diff_find_options *opts, size_t delta_idx, void **cache) @@ -674,42 +786,49 @@ GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) } GIT_INLINE(void) delta_make_rename( - git_diff_delta *to, const git_diff_delta *from, uint32_t similarity) + git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) { to->status = GIT_DELTA_RENAMED; to->similarity = similarity; + to->nfiles = 2; memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; } typedef struct { - uint32_t idx; - uint32_t similarity; + size_t idx; + uint16_t similarity; } diff_find_match; int git_diff_find_similar( - git_diff_list *diff, - git_diff_find_options *given_opts) + git_diff *diff, + const git_diff_find_options *given_opts) { - size_t i, j, sigcache_size; - int error = 0, similarity; - git_diff_delta *from, *to; + size_t s, t; + int error = 0, result; + uint16_t similarity; + git_diff_delta *src, *tgt; git_diff_find_options opts; - size_t num_srcs = 0, num_tgts = 0, tried_srcs = 0, tried_tgts = 0; + size_t num_deltas, num_srcs = 0, num_tgts = 0; + size_t tried_srcs = 0, tried_tgts = 0; size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; void **sigcache; /* cache of similarity metric file signatures */ - diff_find_match *match_srcs = NULL, *match_tgts = NULL, *best_match; + diff_find_match *tgt2src = NULL; + diff_find_match *src2tgt = NULL; + diff_find_match *tgt2src_copy = NULL; + diff_find_match *best_match; git_diff_file swap; if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) return error; + num_deltas = diff->deltas.length; + /* TODO: maybe abort if deltas.length > rename_limit ??? */ - if (!git__is_uint32(diff->deltas.length)) + if (!git__is_uint32(num_deltas)) return 0; - sigcache_size = diff->deltas.length * 2; /* keep size b/c diff may change */ - sigcache = git__calloc(sigcache_size, sizeof(void *)); + sigcache = git__calloc(num_deltas * 2, sizeof(void *)); GITERR_CHECK_ALLOC(sigcache); /* Label rename sources and targets @@ -717,22 +836,30 @@ int git_diff_find_similar( * This will also set self-similarity scores for MODIFIED files and * mark them for splitting if break-rewrites is enabled */ - git_vector_foreach(&diff->deltas, i, to) { - if (is_rename_source(diff, &opts, i, sigcache)) + git_vector_foreach(&diff->deltas, t, tgt) { + if (is_rename_source(diff, &opts, t, sigcache)) ++num_srcs; - if (is_rename_target(diff, &opts, i, sigcache)) + if (is_rename_target(diff, &opts, t, sigcache)) ++num_tgts; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) + num_rewrites++; } /* if there are no candidate srcs or tgts, we're done */ if (!num_srcs || !num_tgts) goto cleanup; - match_tgts = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - GITERR_CHECK_ALLOC(match_tgts); - match_srcs = git__calloc(diff->deltas.length, sizeof(diff_find_match)); - GITERR_CHECK_ALLOC(match_srcs); + src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(src2tgt); + tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(tgt2src); + + if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); + GITERR_CHECK_ALLOC(tgt2src_copy); + } /* * Find best-fit matches for rename / copy candidates @@ -741,47 +868,62 @@ int git_diff_find_similar( find_best_matches: tried_tgts = num_bumped = 0; - git_vector_foreach(&diff->deltas, i, to) { + git_vector_foreach(&diff->deltas, t, tgt) { /* skip things that are not rename targets */ - if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) continue; tried_srcs = 0; - git_vector_foreach(&diff->deltas, j, from) { + git_vector_foreach(&diff->deltas, s, src) { /* skip things that are not rename sources */ - if ((from->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) + if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) continue; /* calculate similarity for this pair and find best match */ - if (i == j) - similarity = -1; /* don't measure self-similarity here */ + if (s == t) + result = -1; /* don't measure self-similarity here */ else if ((error = similarity_measure( - &similarity, diff, &opts, sigcache, 2 * j, 2 * i + 1)) < 0) + &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) goto cleanup; - /* if this pairing is better for the src and the tgt, keep it */ - if (similarity > 0 && - match_tgts[i].similarity < (uint32_t)similarity && - match_srcs[j].similarity < (uint32_t)similarity) + if (result < 0) + continue; + similarity = (uint16_t)result; + + /* is this a better rename? */ + if (tgt2src[t].similarity < similarity && + src2tgt[s].similarity < similarity) { - if (match_tgts[i].similarity > 0) { - match_tgts[match_srcs[j].idx].similarity = 0; - match_srcs[match_tgts[i].idx].similarity = 0; - ++num_bumped; + /* eject old mapping */ + if (src2tgt[s].similarity > 0) { + tgt2src[src2tgt[s].idx].similarity = 0; + num_bumped++; + } + if (tgt2src[t].similarity > 0) { + src2tgt[tgt2src[t].idx].similarity = 0; + num_bumped++; } - match_tgts[i].similarity = (uint32_t)similarity; - match_tgts[i].idx = (uint32_t)j; + /* write new mapping */ + tgt2src[t].idx = s; + tgt2src[t].similarity = similarity; + src2tgt[s].idx = t; + src2tgt[s].similarity = similarity; + } - match_srcs[j].similarity = (uint32_t)similarity; - match_srcs[j].idx = (uint32_t)i; + /* keep best absolute match for copies */ + if (tgt2src_copy != NULL && + tgt2src_copy[t].similarity < similarity) + { + tgt2src_copy[t].idx = s; + tgt2src_copy[t].similarity = similarity; } if (++tried_srcs >= num_srcs) break; - /* cap on maximum targets we'll examine (per "to" file) */ + /* cap on maximum targets we'll examine (per "tgt" file) */ if (tried_srcs > opts.rename_limit) break; } @@ -799,18 +941,21 @@ find_best_matches: tried_tgts = 0; - git_vector_foreach(&diff->deltas, i, to) { + git_vector_foreach(&diff->deltas, t, tgt) { /* skip things that are not rename targets */ - if ((to->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) continue; /* check if this delta was the target of a similarity */ - best_match = &match_tgts[i]; - if (!best_match->similarity) + if (tgt2src[t].similarity) + best_match = &tgt2src[t]; + else if (tgt2src_copy && tgt2src_copy[t].similarity) + best_match = &tgt2src_copy[t]; + else continue; - j = best_match->idx; - from = GIT_VECTOR_GET(&diff->deltas, j); + s = best_match->idx; + src = GIT_VECTOR_GET(&diff->deltas, s); /* possible scenarios: * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME @@ -820,101 +965,114 @@ find_best_matches: * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY */ - if (from->status == GIT_DELTA_DELETED) { + if (src->status == GIT_DELTA_DELETED) { - if (delta_is_new_only(to)) { + if (delta_is_new_only(tgt)) { if (best_match->similarity < opts.rename_threshold) continue; - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); - from->flags |= GIT_DIFF_FLAG__TO_DELETE; + src->flags |= GIT_DIFF_FLAG__TO_DELETE; num_rewrites++; } else { - assert(delta_is_split(to)); + assert(delta_is_split(tgt)); if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - memcpy(&swap, &to->old_file, sizeof(swap)); + memcpy(&swap, &tgt->old_file, sizeof(swap)); - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); num_rewrites--; - from->status = GIT_DELTA_DELETED; - memcpy(&from->old_file, &swap, sizeof(from->old_file)); - memset(&from->new_file, 0, sizeof(from->new_file)); - from->new_file.path = from->old_file.path; - from->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; + assert(src->status == GIT_DELTA_DELETED); + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + memset(&src->new_file, 0, sizeof(src->new_file)); + src->new_file.path = src->old_file.path; + src->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; num_updates++; + + if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; + } } } - else if (delta_is_split(from)) { + else if (delta_is_split(src)) { - if (delta_is_new_only(to)) { + if (delta_is_new_only(tgt)) { if (best_match->similarity < opts.rename_threshold) continue; - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); - from->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? + src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; - memset(&from->old_file, 0, sizeof(from->old_file)); - from->old_file.path = from->new_file.path; - from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; + src->nfiles = 1; + memset(&src->old_file, 0, sizeof(src->old_file)); + src->old_file.path = src->new_file.path; + src->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; - from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; num_rewrites--; num_updates++; } else { - assert(delta_is_split(from)); + assert(delta_is_split(src)); if (best_match->similarity < opts.rename_from_rewrite_threshold) continue; - memcpy(&swap, &to->old_file, sizeof(swap)); + memcpy(&swap, &tgt->old_file, sizeof(swap)); - delta_make_rename(to, from, best_match->similarity); + delta_make_rename(tgt, src, best_match->similarity); num_rewrites--; num_updates++; - memcpy(&from->old_file, &swap, sizeof(from->old_file)); + memcpy(&src->old_file, &swap, sizeof(src->old_file)); /* if we've just swapped the new element into the correct * place, clear the SPLIT flag */ - if (match_tgts[j].idx == i && - match_tgts[j].similarity > + if (tgt2src[s].idx == t && + tgt2src[s].similarity > opts.rename_from_rewrite_threshold) { - - from->status = GIT_DELTA_RENAMED; - from->similarity = match_tgts[j].similarity; - match_tgts[j].similarity = 0; - from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + src->status = GIT_DELTA_RENAMED; + src->similarity = tgt2src[s].similarity; + tgt2src[s].similarity = 0; + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; num_rewrites--; } /* otherwise, if we just overwrote a source, update mapping */ - else if (j > i && match_srcs[i].similarity > 0) { - match_tgts[match_srcs[i].idx].idx = j; + else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; } num_updates++; } } - else if (delta_is_new_only(to)) { - if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES) || - best_match->similarity < opts.copy_threshold) + else if (delta_is_new_only(tgt)) { + if (!FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) continue; - to->status = GIT_DELTA_COPIED; - to->similarity = best_match->similarity; - memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + if (tgt2src_copy[t].similarity < opts.copy_threshold) + continue; + + /* always use best possible source for copy */ + best_match = &tgt2src_copy[t]; + src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); + + tgt->status = GIT_DELTA_COPIED; + tgt->similarity = best_match->similarity; + tgt->nfiles = 2; + memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); num_updates++; } @@ -927,15 +1085,17 @@ find_best_matches: if (num_rewrites > 0 || num_updates > 0) error = apply_splits_and_deletes( diff, diff->deltas.length - num_rewrites, - FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES)); + FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && + !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); cleanup: - git__free(match_srcs); - git__free(match_tgts); + git__free(tgt2src); + git__free(src2tgt); + git__free(tgt2src_copy); - for (i = 0; i < sigcache_size; ++i) { - if (sigcache[i] != NULL) - opts.metric->free_signature(sigcache[i], opts.metric->payload); + for (t = 0; t < num_deltas * 2; ++t) { + if (sigcache[t] != NULL) + opts.metric->free_signature(sigcache[t], opts.metric->payload); } git__free(sigcache); diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c index 7694fb996..e0bc11f7f 100644 --- a/src/diff_xdiff.c +++ b/src/diff_xdiff.c @@ -24,26 +24,26 @@ static int git_xdiff_scan_int(const char **str, int *value) return (digits > 0) ? 0 : -1; } -static int git_xdiff_parse_hunk(git_diff_range *range, const char *header) +static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) { /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ if (*header != '@') return -1; - if (git_xdiff_scan_int(&header, &range->old_start) < 0) + if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) return -1; if (*header == ',') { - if (git_xdiff_scan_int(&header, &range->old_lines) < 0) + if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) return -1; } else - range->old_lines = 1; - if (git_xdiff_scan_int(&header, &range->new_start) < 0) + hunk->old_lines = 1; + if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) return -1; if (*header == ',') { - if (git_xdiff_scan_int(&header, &range->new_lines) < 0) + if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) return -1; } else - range->new_lines = 1; - if (range->old_start < 0 || range->new_start < 0) + hunk->new_lines = 1; + if (hunk->old_start < 0 || hunk->new_start < 0) return -1; return 0; @@ -51,38 +51,104 @@ static int git_xdiff_parse_hunk(git_diff_range *range, const char *header) typedef struct { git_xdiff_output *xo; - git_diff_patch *patch; - git_diff_range range; + git_patch *patch; + git_diff_hunk hunk; + int old_lineno, new_lineno; + mmfile_t xd_old_data, xd_new_data; } git_xdiff_info; +static int diff_update_lines( + git_xdiff_info *info, + git_diff_line *line, + const char *content, + size_t content_len) +{ + const char *scan = content, *scan_end = content + content_len; + + for (line->num_lines = 0; scan < scan_end; ++scan) + if (*scan == '\n') + ++line->num_lines; + + line->content = content; + line->content_len = content_len; + + /* expect " "/"-"/"+", then data */ + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_DEL_EOFNL: + line->old_lineno = -1; + line->new_lineno = info->new_lineno; + info->new_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_ADD_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = -1; + info->old_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_CONTEXT_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = info->new_lineno; + info->old_lineno += (int)line->num_lines; + info->new_lineno += (int)line->num_lines; + break; + default: + giterr_set(GITERR_INVALID, "Unknown diff line origin %02x", + (unsigned int)line->origin); + return -1; + } + + return 0; +} + static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) { git_xdiff_info *info = priv; - git_diff_patch *patch = info->patch; - const git_diff_delta *delta = git_diff_patch_delta(patch); + git_patch *patch = info->patch; + const git_diff_delta *delta = git_patch_get_delta(patch); git_diff_output *output = &info->xo->output; + git_diff_line line; if (len == 1) { - output->error = git_xdiff_parse_hunk(&info->range, bufs[0].ptr); + output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); if (output->error < 0) return output->error; + info->hunk.header_len = bufs[0].size; + if (info->hunk.header_len >= sizeof(info->hunk.header)) + info->hunk.header_len = sizeof(info->hunk.header) - 1; + memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); + info->hunk.header[info->hunk.header_len] = '\0'; + if (output->hunk_cb != NULL && - output->hunk_cb(delta, &info->range, - bufs[0].ptr, bufs[0].size, output->payload)) + output->hunk_cb(delta, &info->hunk, output->payload)) output->error = GIT_EUSER; + + info->old_lineno = info->hunk.old_start; + info->new_lineno = info->hunk.new_start; } if (len == 2 || len == 3) { /* expect " "/"-"/"+", then data */ - char origin = + line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : GIT_DIFF_LINE_CONTEXT; - if (output->data_cb != NULL && - output->data_cb(delta, &info->range, - origin, bufs[1].ptr, bufs[1].size, output->payload)) + if (line.origin == GIT_DIFF_LINE_ADDITION) + line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; + else if (line.origin == GIT_DIFF_LINE_DELETION) + line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; + else + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[1].ptr, bufs[1].size); + + if (!output->error && + output->data_cb != NULL && + output->data_cb(delta, &info->hunk, &line, output->payload)) output->error = GIT_EUSER; } @@ -92,26 +158,30 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) * If we have a '-' and a third buf, then we have removed a line * with out a newline but added a blank line, so ADD_EOFNL. */ - char origin = + line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : GIT_DIFF_LINE_CONTEXT_EOFNL; - if (output->data_cb != NULL && - output->data_cb(delta, &info->range, - origin, bufs[2].ptr, bufs[2].size, output->payload)) + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[2].ptr, bufs[2].size); + + if (!output->error && + output->data_cb != NULL && + output->data_cb(delta, &info->hunk, &line, output->payload)) output->error = GIT_EUSER; } return output->error; } -static int git_xdiff(git_diff_output *output, git_diff_patch *patch) +static int git_xdiff(git_diff_output *output, git_patch *patch) { git_xdiff_output *xo = (git_xdiff_output *)output; git_xdiff_info info; git_diff_find_context_payload findctxt; - mmfile_t xd_old_data, xd_new_data; memset(&info, 0, sizeof(info)); info.patch = patch; @@ -120,7 +190,7 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) xo->callback.priv = &info; git_diff_find_context_init( - &xo->config.find_func, &findctxt, git_diff_patch__driver(patch)); + &xo->config.find_func, &findctxt, git_patch__driver(patch)); xo->config.find_func_priv = &findctxt; if (xo->config.find_func != NULL) @@ -132,10 +202,10 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) * updates are needed to xo->params.flags */ - git_diff_patch__old_data(&xd_old_data.ptr, &xd_old_data.size, patch); - git_diff_patch__new_data(&xd_new_data.ptr, &xd_new_data.size, 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); - xdl_diff(&xd_old_data, &xd_new_data, + xdl_diff(&info.xd_old_data, &info.xd_new_data, &xo->params, &xo->config, &xo->callback); git_diff_find_context_clear(&findctxt); @@ -145,7 +215,7 @@ static int git_xdiff(git_diff_output *output, git_diff_patch *patch) void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) { - uint32_t flags = opts ? opts->flags : GIT_DIFF_NORMAL; + uint32_t flags = opts ? opts->flags : 0; xo->output.diff_cb = git_xdiff; @@ -161,6 +231,11 @@ void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (flags & GIT_DIFF_PATIENCE) + xo->params.flags |= XDF_PATIENCE_DIFF; + if (flags & GIT_DIFF_MINIMAL) + xo->params.flags |= XDF_NEED_MINIMAL; + memset(&xo->callback, 0, sizeof(xo->callback)); xo->callback.outf = git_xdiff_cb; } diff --git a/src/errors.c b/src/errors.c index e2629f69e..d04da4ca9 100644 --- a/src/errors.c +++ b/src/errors.c @@ -112,8 +112,25 @@ 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; } - diff --git a/src/fetch.c b/src/fetch.c index 03fad5fec..276591821 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -19,55 +19,47 @@ #include "repository.h" #include "refs.h" -struct filter_payload { - git_remote *remote; - const git_refspec *spec, *tagspec; - git_odb *odb; - int found_head; -}; - -static int filter_ref__cb(git_remote_head *head, void *payload) +static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, git_refspec *tagspec) { - struct filter_payload *p = payload; int match = 0; if (!git_reference_is_valid_name(head->name)) return 0; - if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) - p->found_head = 1; - else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { + if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { /* * If tagopt is --tags, then we only use the default * tags refspec and ignore the remote's */ - if (git_refspec_src_matches(p->tagspec, head->name)) + if (git_refspec_src_matches(tagspec, head->name)) match = 1; else return 0; - } else if (git_remote__matching_refspec(p->remote, head->name)) + } else if (git_remote__matching_refspec(remote, head->name)) match = 1; if (!match) return 0; /* If we have the object, mark it so we don't ask for it */ - if (git_odb_exists(p->odb, &head->oid)) + if (git_odb_exists(odb, &head->oid)) head->local = 1; else - p->remote->need_pack = 1; + remote->need_pack = 1; - return git_vector_insert(&p->remote->refs, head); + return git_vector_insert(&remote->refs, head); } static int filter_wants(git_remote *remote) { - struct filter_payload p; - git_refspec tagspec; - int error = -1; + git_remote_head **heads; + git_refspec tagspec, head; + int error = 0; + git_odb *odb; + size_t i, heads_len; git_vector_clear(&remote->refs); - if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) + if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0) return error; /* @@ -76,14 +68,27 @@ static int filter_wants(git_remote *remote) * not interested in any particular branch but just the remote's * HEAD, which will be stored in FETCH_HEAD after the fetch. */ - p.tagspec = &tagspec; - p.found_head = 0; - p.remote = remote; + if (remote->active_refspecs.length == 0) { + if ((error = git_refspec__parse(&head, "HEAD", true)) < 0) + goto cleanup; - if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0) + error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs); + git_refspec__free(&head); + + if (error < 0) + goto cleanup; + } + + if (git_repository_odb__weakptr(&odb, remote->repo) < 0) goto cleanup; - error = git_remote_ls(remote, filter_ref__cb, &p); + if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + if ((error = maybe_want(remote, heads[i], odb, &tagspec)) < 0) + break; + } cleanup: git_refspec__free(&tagspec); @@ -106,7 +111,7 @@ int git_fetch_negotiate(git_remote *remote) } /* Don't try to negotiate when we don't want anything */ - if (remote->refs.length == 0 || !remote->need_pack) + if (!remote->need_pack) return 0; /* @@ -119,15 +124,13 @@ int git_fetch_negotiate(git_remote *remote) remote->refs.length); } -int git_fetch_download_pack( - git_remote *remote, - git_transfer_progress_callback progress_cb, - void *progress_payload) +int git_fetch_download_pack(git_remote *remote) { git_transport *t = remote->transport; if(!remote->need_pack) return 0; - return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload); + return t->download_pack(t, remote->repo, &remote->stats, + remote->callbacks.transfer_progress, remote->callbacks.payload); } diff --git a/src/fetch.h b/src/fetch.h index 059251d04..9605da1b5 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -11,10 +11,7 @@ int git_fetch_negotiate(git_remote *remote); -int git_fetch_download_pack( - git_remote *remote, - git_transfer_progress_callback progress_cb, - void *progress_payload); +int git_fetch_download_pack(git_remote *remote); int git_fetch__download_pack( git_transport *t, diff --git a/src/fetchhead.c b/src/fetchhead.c index 4dcebb857..67089d13d 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -74,6 +74,7 @@ static int fetchhead_ref_write( { char oid[GIT_OID_HEXSZ + 1]; const char *type, *name; + int head = 0; assert(file && fetchhead_ref); @@ -87,11 +88,16 @@ static int fetchhead_ref_write( GIT_REFS_TAGS_DIR) == 0) { type = "tag "; name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR); + } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) { + head = 1; } else { type = ""; name = fetchhead_ref->ref_name; } + if (head) + return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url); + return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n", oid, (fetchhead_ref->is_merge) ? "" : "not-for-merge", @@ -112,7 +118,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0) return -1; - if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) { + if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { git_buf_free(&path); return -1; } @@ -124,7 +130,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) git_vector_foreach(fetchhead_refs, i, fetchhead_ref) fetchhead_ref_write(&file, fetchhead_ref); - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); + return git_filebuf_commit(&file); } static int fetchhead_ref_parse( diff --git a/src/filebuf.c b/src/filebuf.c index 246ae34e7..9c3dae811 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -10,8 +10,6 @@ #include "filebuf.h" #include "fileops.h" -#define GIT_LOCK_FILE_MODE 0644 - static const size_t WRITE_BUFFER_SIZE = (4096 * 2); enum buferr_t { @@ -44,7 +42,7 @@ static int verify_last_error(git_filebuf *file) } } -static int lock_file(git_filebuf *file, int flags) +static int lock_file(git_filebuf *file, int flags, mode_t mode) { if (git_path_exists(file->path_lock) == true) { if (flags & GIT_FILEBUF_FORCE) @@ -53,20 +51,20 @@ static int lock_file(git_filebuf *file, int flags) giterr_clear(); /* actual OS error code just confuses */ giterr_set(GITERR_OS, "Failed to lock file '%s' for writing", file->path_lock); - return -1; + return GIT_ELOCKED; } } /* create path to the file buffer is required */ if (flags & GIT_FILEBUF_FORCE) { /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ - file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, GIT_LOCK_FILE_MODE); + file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); } else { - file->fd = git_futils_creat_locked(file->path_lock, GIT_LOCK_FILE_MODE); + file->fd = git_futils_creat_locked(file->path_lock, mode); } if (file->fd < 0) - return -1; + return file->fd; file->fd_is_open = true; @@ -195,9 +193,9 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) return 0; } -int git_filebuf_open(git_filebuf *file, const char *path, int flags) +int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) { - int compression; + int compression, error = -1; size_t path_len; /* opening an already open buffer is a programming error; @@ -255,7 +253,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) git_buf tmp_path = GIT_BUF_INIT; /* Open the file as temporary for locking */ - file->fd = git_futils_mktmp(&tmp_path, path); + file->fd = git_futils_mktmp(&tmp_path, path, mode); if (file->fd < 0) { git_buf_free(&tmp_path); @@ -282,7 +280,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); /* open the file for locking */ - if (lock_file(file, flags) < 0) + if ((error = lock_file(file, flags, mode)) < 0) goto cleanup; } @@ -290,7 +288,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) cleanup: git_filebuf_cleanup(file); - return -1; + return error; } int git_filebuf_hash(git_oid *oid, git_filebuf *file) @@ -309,16 +307,16 @@ int git_filebuf_hash(git_oid *oid, git_filebuf *file) return 0; } -int git_filebuf_commit_at(git_filebuf *file, const char *path, mode_t mode) +int git_filebuf_commit_at(git_filebuf *file, const char *path) { git__free(file->path_original); file->path_original = git__strdup(path); GITERR_CHECK_ALLOC(file->path_original); - return git_filebuf_commit(file, mode); + return git_filebuf_commit(file); } -int git_filebuf_commit(git_filebuf *file, mode_t mode) +int git_filebuf_commit(git_filebuf *file) { /* temporary files cannot be committed */ assert(file && file->path_original); @@ -338,11 +336,6 @@ int git_filebuf_commit(git_filebuf *file, mode_t mode) file->fd = -1; - if (p_chmod(file->path_lock, mode)) { - giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); - goto on_error; - } - p_unlink(file->path_original); if (p_rename(file->path_lock, file->path_original) < 0) { diff --git a/src/filebuf.h b/src/filebuf.h index 823af81bf..044af5405 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -77,9 +77,9 @@ int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); -int git_filebuf_open(git_filebuf *lock, const char *path, int flags); -int git_filebuf_commit(git_filebuf *lock, mode_t mode); -int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); +int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_commit(git_filebuf *lock); +int git_filebuf_commit_at(git_filebuf *lock, const char *path); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); int git_filebuf_flush(git_filebuf *file); diff --git a/src/fileops.c b/src/fileops.c index d5f6acfad..5763b370b 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -6,6 +6,7 @@ */ #include "common.h" #include "fileops.h" +#include "global.h" #include #if GIT_WIN32 #include "win32/findfile.h" @@ -18,9 +19,12 @@ int git_futils_mkpath2file(const char *file_path, const mode_t mode) GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } -int git_futils_mktmp(git_buf *path_out, const char *filename) +int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode) { int fd; + mode_t mask; + + p_umask(mask = p_umask(0)); git_buf_sets(path_out, filename); git_buf_puts(path_out, "_git2_XXXXXX"); @@ -34,6 +38,12 @@ int git_futils_mktmp(git_buf *path_out, const char *filename) return -1; } + if (p_chmod(path_out->ptr, (mode & ~mask))) { + giterr_set(GITERR_OS, + "Failed to set permissions on file '%s'", path_out->ptr); + return -1; + } + return fd; } @@ -55,22 +65,12 @@ int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode int git_futils_creat_locked(const char *path, const mode_t mode) { - int fd; - -#ifdef GIT_WIN32 - wchar_t buf[GIT_WIN_PATH]; - - git__utf8_to_16(buf, GIT_WIN_PATH, path); - fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); -#else - fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | - O_EXCL | O_BINARY | O_CLOEXEC, mode); -#endif if (fd < 0) { giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); - return -1; + return errno == EEXIST ? GIT_ELOCKED : -1; } return fd; @@ -87,11 +87,8 @@ int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, con int git_futils_open_ro(const char *path) { int fd = p_open(path, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT || errno == ENOTDIR) - fd = GIT_ENOTFOUND; - giterr_set(GITERR_OS, "Failed to open '%s'", path); - } + if (fd < 0) + return git_path_set_error(errno, path, "open"); return fd; } @@ -110,7 +107,7 @@ git_off_t git_futils_filesize(git_file fd) mode_t git_futils_canonical_mode(mode_t raw_mode) { if (S_ISREG(raw_mode)) - return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); + return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); else if (S_ISLNK(raw_mode)) return S_IFLNK; else if (S_ISGITLINK(raw_mode)) @@ -156,11 +153,16 @@ int git_futils_readbuffer_updated( if (updated != NULL) *updated = 0; - if ((fd = git_futils_open_ro(path)) < 0) - return fd; + if (p_stat(path, &st) < 0) + return git_path_set_error(errno, path, "stat"); - if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { - p_close(fd); + + if (S_ISDIR(st.st_mode)) { + giterr_set(GITERR_INVALID, "requested file is a directory"); + return GIT_ENOTFOUND; + } + + if (!git__is_sizet(st.st_size+1)) { giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); return -1; } @@ -177,7 +179,6 @@ int git_futils_readbuffer_updated( changed = true; if (!changed) { - p_close(fd); return 0; } @@ -186,6 +187,9 @@ int git_futils_readbuffer_updated( 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) { p_close(fd); return -1; @@ -222,6 +226,7 @@ int git_futils_writebuffer( if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) { giterr_set(GITERR_OS, "Could not write to '%s'", path); (void)p_close(fd); + return error; } if ((error = p_close(fd)) < 0) @@ -347,12 +352,11 @@ int git_futils_mkdir( /* make directory */ if (p_mkdir(make_path.ptr, mode) < 0) { - int tmp_errno = errno; + int tmp_errno = giterr_system_last(); /* ignore error if directory already exists */ - if (p_stat(make_path.ptr, &st) < 0 || - !(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { - errno = tmp_errno; + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + giterr_system_set(tmp_errno); giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); goto done; } @@ -400,8 +404,11 @@ typedef struct { size_t baselen; uint32_t flags; int error; + int depth; } futils__rmdir_data; +#define FUTILS_MAX_DEPTH 100 + static int futils__error_cannot_rmdir(const char *path, const char *filemsg) { if (filemsg) @@ -440,51 +447,60 @@ static int futils__rm_first_parent(git_buf *path, const char *ceiling) static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) { - struct stat st; futils__rmdir_data *data = opaque; + int error = data->error; + struct stat st; - if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (data->depth > FUTILS_MAX_DEPTH) + error = futils__error_cannot_rmdir( + path->ptr, "directory nesting too deep"); + + else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { if (errno == ENOENT) - data->error = 0; + error = 0; else if (errno == ENOTDIR) { /* asked to remove a/b/c/d/e and a/b is a normal file */ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) - data->error = futils__rm_first_parent(path, data->base); + error = futils__rm_first_parent(path, data->base); else futils__error_cannot_rmdir( path->ptr, "parent is not directory"); } else - futils__error_cannot_rmdir(path->ptr, "cannot access"); + error = git_path_set_error(errno, path->ptr, "rmdir"); } else if (S_ISDIR(st.st_mode)) { - int error = git_path_direach(path, futils__rmdir_recurs_foreach, data); + data->depth++; + + error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data); if (error < 0) return (error == GIT_EUSER) ? data->error : error; - data->error = p_rmdir(path->ptr); + data->depth--; - if (data->error < 0) { + if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) + return data->error; + + if ((error = p_rmdir(path->ptr)) < 0) { if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) - data->error = 0; + error = 0; else - futils__error_cannot_rmdir(path->ptr, NULL); + error = git_path_set_error(errno, path->ptr, "rmdir"); } } else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { - data->error = p_unlink(path->ptr); - - if (data->error < 0) - futils__error_cannot_rmdir(path->ptr, "cannot be removed"); + if (p_unlink(path->ptr) < 0) + error = git_path_set_error(errno, path->ptr, "remove"); } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) - data->error = futils__error_cannot_rmdir(path->ptr, "still present"); + error = futils__error_cannot_rmdir(path->ptr, "still present"); - return data->error; + data->error = error; + return error; } static int futils__rmdir_empty_parent(void *opaque, git_buf *path) @@ -507,7 +523,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path) giterr_clear(); error = GIT_ITEROVER; } else { - futils__error_cannot_rmdir(git_buf_cstr(path), NULL); + error = git_path_set_error(errno, git_buf_cstr(path), "rmdir"); } } @@ -519,7 +535,7 @@ int git_futils_rmdir_r( { int error; git_buf fullpath = GIT_BUF_INIT; - futils__rmdir_data data; + futils__rmdir_data data = { 0 }; /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) @@ -528,7 +544,6 @@ int git_futils_rmdir_r( data.base = base ? base : ""; data.baselen = base ? strlen(base) : 0; data.flags = flags; - data.error = 0; error = futils__rmdir_recurs_foreach(&data, &fullpath); @@ -546,46 +561,11 @@ int git_futils_rmdir_r( return error; } -int git_futils_cleanupdir_r(const char *path) -{ - int error; - git_buf fullpath = GIT_BUF_INIT; - futils__rmdir_data data; - - if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0) - goto clean_up; - - data.base = ""; - data.baselen = 0; - data.flags = GIT_RMDIR_REMOVE_FILES; - data.error = 0; - - if (!git_path_exists(path)) { - giterr_set(GITERR_OS, "Path does not exist: %s" , path); - error = GIT_ERROR; - goto clean_up; - } - - if (!git_path_isdir(path)) { - giterr_set(GITERR_OS, "Path is not a directory: %s" , path); - error = GIT_ERROR; - goto clean_up; - } - - error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data); - if (error == GIT_EUSER) - error = data.error; - -clean_up: - git_buf_free(&fullpath); - return error; -} - static int git_futils_guess_system_dirs(git_buf *out) { #ifdef GIT_WIN32 - return git_win32__find_system_dirs(out); + return git_win32__find_system_dirs(out, L"etc\\"); #else return git_buf_sets(out, "/etc"); #endif @@ -617,17 +597,48 @@ static int git_futils_guess_xdg_dirs(git_buf *out) #endif } +static int git_futils_guess_template_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_system_dirs(out, L"share\\git-core\\templates"); +#else + return git_buf_sets(out, "/usr/share/git-core/templates"); +#endif +} + typedef int (*git_futils_dirs_guess_cb)(git_buf *out); static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] = - { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; + { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { git_futils_guess_system_dirs, git_futils_guess_global_dirs, git_futils_guess_xdg_dirs, + git_futils_guess_template_dirs, }; +void git_futils_dirs_global_shutdown(void) +{ + int i; + for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) + git_buf_free(&git_futils__dirs[i]); +} + +int git_futils_dirs_global_init(void) +{ + git_futils_dir_t i; + const git_buf *path; + int error = 0; + + for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++) + error = git_futils_dirs_get(&path, i); + + git__on_shutdown(git_futils_dirs_global_shutdown); + + return error; +} + static int git_futils_check_selector(git_futils_dir_t which) { if (which < GIT_FUTILS_DIR__MAX) @@ -707,13 +718,6 @@ int git_futils_dirs_set(git_futils_dir_t which, const char *search_path) return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0; } -void git_futils_dirs_free(void) -{ - int i; - for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) - git_buf_free(&git_futils__dirs[i]); -} - static int git_futils_find_in_dirlist( git_buf *path, const char *name, git_futils_dir_t which, const char *label) { @@ -734,7 +738,8 @@ static int git_futils_find_in_dirlist( continue; GITERR_CHECK_ERROR(git_buf_set(path, scan, len)); - GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name)); + if (name) + GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name)); if (git_path_exists(path->ptr)) return 0; @@ -763,6 +768,12 @@ int git_futils_find_xdg_file(git_buf *path, const char *filename) path, filename, GIT_FUTILS_DIR_XDG, "global/xdg"); } +int git_futils_find_template_dir(git_buf *path) +{ + return git_futils_find_in_dirlist( + path, NULL, GIT_FUTILS_DIR_TEMPLATE, "template"); +} + int git_futils_fake_symlink(const char *old, const char *new) { int retcode = GIT_ERROR; @@ -807,11 +818,8 @@ int git_futils_cp(const char *from, const char *to, mode_t filemode) return ifd; if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { - if (errno == ENOENT || errno == ENOTDIR) - ofd = GIT_ENOTFOUND; - giterr_set(GITERR_OS, "Failed to open '%s' for writing", to); p_close(ifd); - return ofd; + return git_path_set_error(errno, to, "open for writing"); } return cp_by_fd(ifd, ofd, true); @@ -850,6 +858,7 @@ typedef struct { uint32_t flags; uint32_t mkdir_flags; mode_t dirmode; + int error; } cp_r_info; #define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) @@ -888,20 +897,22 @@ static int _cp_r_callback(void *ref, git_buf *from) return 0; if (git_buf_joinpath( - &info->to, info->to_root, from->ptr + info->from_prefix) < 0) - return -1; + &info->to, info->to_root, from->ptr + info->from_prefix) < 0) { + error = -1; + goto exit; + } - if (p_lstat(info->to.ptr, &to_st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) { - giterr_set(GITERR_OS, - "Could not access %s while copying files", info->to.ptr); - return -1; - } - } else + if (!(error = git_path_lstat(info->to.ptr, &to_st))) exists = true; + else if (error != GIT_ENOTFOUND) + goto exit; + else { + giterr_clear(); + error = 0; + } if ((error = git_path_lstat(from->ptr, &from_st)) < 0) - return error; + goto exit; if (S_ISDIR(from_st.st_mode)) { mode_t oldmode = info->dirmode; @@ -915,13 +926,17 @@ static int _cp_r_callback(void *ref, git_buf *from) error = _cp_r_mkdir(info, from); /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode))) - error = git_path_direach(from, _cp_r_callback, info); + if (!error && (!exists || S_ISDIR(to_st.st_mode))) { + error = git_path_direach(from, 0, _cp_r_callback, info); + + if (error == GIT_EUSER) + error = info->error; + } if (oldmode != 0) info->dirmode = oldmode; - return error; + goto exit; } if (exists) { @@ -931,7 +946,8 @@ static int _cp_r_callback(void *ref, git_buf *from) if (p_unlink(info->to.ptr) < 0) { giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'", info->to.ptr); - return -1; + error = -1; + goto exit; } } @@ -944,7 +960,7 @@ static int _cp_r_callback(void *ref, git_buf *from) /* Make container directory on demand if needed */ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && (error = _cp_r_mkdir(info, from)) < 0) - return error; + goto exit; /* make symlink or regular file */ if (S_ISLNK(from_st.st_mode)) @@ -953,11 +969,13 @@ static int _cp_r_callback(void *ref, git_buf *from) mode_t usemode = from_st.st_mode; if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) - usemode = (usemode & 0111) ? 0777 : 0666; + usemode = GIT_PERMS_FOR_WRITE(usemode); error = git_futils_cp(from->ptr, info->to.ptr, usemode); } +exit: + info->error = error; return error; } @@ -978,6 +996,7 @@ int git_futils_cp_r( info.flags = flags; info.dirmode = dirmode; info.from_prefix = path.size; + info.error = 0; git_buf_init(&info.to, 0); /* precalculate mkdir flags */ @@ -999,6 +1018,9 @@ int git_futils_cp_r( git_buf_free(&path); git_buf_free(&info.to); + if (error == GIT_EUSER) + error = info.error; + return error; } diff --git a/src/fileops.h b/src/fileops.h index f4e059c83..636c9b67d 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -115,12 +115,7 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode); * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base * if removing this item leaves them empty * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR - * - * The old values translate into the new as follows: - * - * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY - * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES - * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY + * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself */ typedef enum { GIT_RMDIR_EMPTY_HIERARCHY = 0, @@ -128,6 +123,7 @@ typedef enum { GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), GIT_RMDIR_EMPTY_PARENTS = (1 << 2), GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), + GIT_RMDIR_SKIP_ROOT = (1 << 4), } git_futils_rmdir_flags; /** @@ -140,20 +136,12 @@ typedef enum { */ extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); -/** - * Remove all files and directories beneath the specified path. - * - * @param path Path to the top level directory to process. - * @return 0 on success; -1 on error. - */ -extern int git_futils_cleanupdir_r(const char *path); - /** * Create and open a temporary file with a `_git2_` suffix. * Writes the filename into path_out. * @return On success, an open file descriptor, else an error code < 0. */ -extern int git_futils_mktmp(git_buf *path_out, const char *filename); +extern int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode); /** * Move a file on the filesystem, create the @@ -223,9 +211,13 @@ extern int git_futils_open_ro(const char *path); */ extern git_off_t git_futils_filesize(git_file fd); +#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) + #define GIT_MODE_PERMS_MASK 0777 -#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) -#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) +#define GIT_MODE_TYPE_MASK 0170000 +#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) #define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) /** @@ -244,7 +236,7 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode); * @param out buffer to populate with the mapping information. * @param fd open descriptor to configure the mapping from. * @param begin first byte to map, this should be page aligned. - * @param end number of bytes to map. + * @param len number of bytes to map. * @return * - 0 on success; * - -1 on error. @@ -278,7 +270,7 @@ extern void git_futils_mmap_free(git_map *map); /** * Find a "global" file (i.e. one in a user's home directory). * - * @param pathbuf buffer to write the full path into + * @param path buffer to write the full path into * @param filename name of file to find in the home directory * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ @@ -287,7 +279,7 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename); /** * Find an "XDG" file (i.e. one in user's XDG config path). * - * @param pathbuf buffer to write the full path into + * @param path buffer to write the full path into * @param filename name of file to find in the home directory * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ @@ -296,19 +288,35 @@ extern int git_futils_find_xdg_file(git_buf *path, const char *filename); /** * Find a "system" file (i.e. one shared for all users of the system). * - * @param pathbuf buffer to write the full path into + * @param path buffer to write the full path into * @param filename name of file to find in the home directory * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ extern int git_futils_find_system_file(git_buf *path, const char *filename); +/** + * Find template directory. + * + * @param path buffer to write the full path into + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_futils_find_template_dir(git_buf *path); + typedef enum { GIT_FUTILS_DIR_SYSTEM = 0, GIT_FUTILS_DIR_GLOBAL = 1, GIT_FUTILS_DIR_XDG = 2, - GIT_FUTILS_DIR__MAX = 3, + GIT_FUTILS_DIR_TEMPLATE = 3, + GIT_FUTILS_DIR__MAX = 4, } git_futils_dir_t; +/** + * Configures global data for configuration file search paths. + * + * @return 0 on success, <0 on failure + */ +extern int git_futils_dirs_global_init(void); + /** * Get the search path for global/system/xdg files * @@ -342,11 +350,6 @@ extern int git_futils_dirs_get_str( */ extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths); -/** - * Release / reset all search paths - */ -extern void git_futils_dirs_free(void); - /** * Create a "fake" symlink (text file containing the target path). * @@ -396,4 +399,9 @@ extern int git_futils_filestamp_check( extern void git_futils_filestamp_set( git_futils_filestamp *tgt, const git_futils_filestamp *src); +/** + * Free the configuration file search paths. + */ +extern void git_futils_dirs_global_shutdown(void); + #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c index 9f749dcbd..9f866fe88 100644 --- a/src/filter.c +++ b/src/filter.c @@ -10,68 +10,594 @@ #include "hash.h" #include "filter.h" #include "repository.h" +#include "global.h" +#include "git2/sys/filter.h" #include "git2/config.h" #include "blob.h" +#include "attr_file.h" +#include "array.h" -int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) +struct git_filter_source { + git_repository *repo; + const char *path; + git_oid oid; /* zero if unknown (which is likely) */ + uint16_t filemode; /* zero if unknown */ + git_filter_mode_t mode; +}; + +typedef struct { + git_filter *filter; + void *payload; +} git_filter_entry; + +struct git_filter_list { + git_array_t(git_filter_entry) filters; + git_filter_source source; + char path[GIT_FLEX_ARRAY]; +}; + +typedef struct { + const char *filter_name; + git_filter *filter; + int priority; + int initialized; + size_t nattrs, nmatches; + char *attrdata; + const char *attrs[GIT_FLEX_ARRAY]; +} git_filter_def; + +static int filter_def_priority_cmp(const void *a, const void *b) +{ + int pa = ((const git_filter_def *)a)->priority; + int pb = ((const git_filter_def *)b)->priority; + return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; +} + +struct filter_registry { + git_vector filters; +}; + +static struct filter_registry *git__filter_registry = NULL; + +static void filter_registry_shutdown(void) +{ + struct filter_registry *reg = NULL; + size_t pos; + git_filter_def *fdef; + + if ((reg = git__swap(git__filter_registry, NULL)) == NULL) + return; + + git_vector_foreach(®->filters, pos, fdef) { + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + 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) +{ + const char *start, *scan = attr_str; + int has_eq; + + *nattr = *nmatch = 0; + + if (!scan) + return 0; + + while (*scan) { + while (git__isspace(*scan)) scan++; + + for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { + if (*scan == '=') + has_eq = 1; + } + + if (scan > start) { + (*nattr)++; + if (has_eq || *start == '-' || *start == '+' || *start == '!') + (*nmatch)++; + + if (has_eq) + git_buf_putc(attrs, '='); + git_buf_put(attrs, start, scan - start); + git_buf_putc(attrs, '\0'); + } + } + + return 0; +} + +static void filter_def_set_attrs(git_filter_def *fdef) +{ + char *scan = fdef->attrdata; + size_t i; + + for (i = 0; i < fdef->nattrs; ++i) { + const char *name, *value; + + switch (*scan) { + case '=': + name = scan + 1; + for (scan++; *scan != '='; scan++) /* find '=' */; + *scan++ = '\0'; + value = scan; + break; + case '-': + name = scan + 1; value = git_attr__false; break; + case '+': + name = scan + 1; value = git_attr__true; break; + case '!': + name = scan + 1; value = git_attr__unset; break; + default: + name = scan; value = NULL; break; + } + + fdef->attrs[i] = name; + fdef->attrs[i + fdef->nattrs] = value; + + scan += strlen(scan) + 1; + } +} + +static int filter_def_name_key_check(const void *key, const void *fdef) +{ + const char *name = + fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; + return name ? git__strcmp(key, name) : -1; +} + +static int filter_def_filter_key_check(const void *key, const void *fdef) +{ + const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; + 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( + const char *name, git_filter *filter, int priority) +{ + git_filter_def *fdef; + size_t nattr = 0, nmatch = 0; + git_buf attrs = GIT_BUF_INIT; + + 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; + + fdef = git__calloc( + sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1); + GITERR_CHECK_ALLOC(fdef); + + fdef->filter_name = name; + fdef->filter = filter; + fdef->priority = priority; + fdef->nattrs = nattr; + fdef->nmatches = nmatch; + fdef->attrdata = git_buf_detach(&attrs); + + filter_def_set_attrs(fdef); + + if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) { + git__free(fdef->attrdata); + git__free(fdef); + return -1; + } + + git_vector_sort(&git__filter_registry->filters); + return 0; +} + +int git_filter_unregister(const char *name) +{ + size_t pos; + git_filter_def *fdef; + + /* cannot unregister default filters */ + if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { + giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", 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; + } + + (void)git_vector_remove(&git__filter_registry->filters, pos); + + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->attrdata); + git__free(fdef); + + return 0; +} + +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; + } + + fdef->initialized = true; + return 0; +} + +git_filter *git_filter_lookup(const char *name) +{ + size_t pos; + git_filter_def *fdef; + + if (filter_registry_initialize() < 0) + return NULL; + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) + return NULL; + + if (!fdef->initialized && filter_initialize(fdef) < 0) + return NULL; + + return fdef->filter; +} + +void git_filter_free(git_filter *filter) +{ + git__free(filter); +} + +git_repository *git_filter_source_repo(const git_filter_source *src) +{ + return src->repo; +} + +const char *git_filter_source_path(const git_filter_source *src) +{ + return src->path; +} + +uint16_t git_filter_source_filemode(const git_filter_source *src) +{ + return src->filemode; +} + +const git_oid *git_filter_source_id(const git_filter_source *src) +{ + return git_oid_iszero(&src->oid) ? NULL : &src->oid; +} + +git_filter_mode_t git_filter_source_mode(const git_filter_source *src) +{ + return src->mode; +} + +static int filter_list_new( + git_filter_list **out, const git_filter_source *src) +{ + git_filter_list *fl = NULL; + size_t pathlen = src->path ? strlen(src->path) : 0; + + fl = git__calloc(1, sizeof(git_filter_list) + pathlen + 1); + GITERR_CHECK_ALLOC(fl); + + if (src->path) + memcpy(fl->path, src->path, pathlen); + fl->source.repo = src->repo; + fl->source.path = fl->path; + fl->source.mode = src->mode; + + *out = fl; + return 0; +} + +static int filter_list_check_attributes( + const char ***out, git_filter_def *fdef, const git_filter_source *src) { int error; - - if (mode == GIT_FILTER_TO_ODB) { - /* Load the CRLF cleanup filter when writing to the ODB */ - error = git_filter_add__crlf_to_odb(filters, repo, path); - if (error < 0) - return error; - } else { - error = git_filter_add__crlf_to_workdir(filters, repo, path); - if (error < 0) - return error; - } - - return (int)filters->length; -} - -void git_filters_free(git_vector *filters) -{ size_t i; - git_filter *filter; + const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); + GITERR_CHECK_ALLOC(strs); - git_vector_foreach(filters, i, filter) { - if (filter->do_free != NULL) - filter->do_free(filter); - else - git__free(filter); - } + error = git_attr_get_many( + strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs); - git_vector_free(filters); -} - -int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) -{ - size_t i; - unsigned int src; - git_buf *dbuffer[2]; - - dbuffer[0] = source; - dbuffer[1] = dest; - - src = 0; - - if (git_buf_len(source) == 0) { - git_buf_clear(dest); + /* if no values were found but no matches are needed, it's okay! */ + if (error == GIT_ENOTFOUND && !fdef->nmatches) { + giterr_clear(); + git__free((void *)strs); return 0; } - /* Pre-grow the destination buffer to more or less the size - * we expect it to have */ - if (git_buf_grow(dest, git_buf_len(source)) < 0) + for (i = 0; !error && i < fdef->nattrs; ++i) { + const char *want = fdef->attrs[fdef->nattrs + i]; + git_attr_t want_type, found_type; + + if (!want) + continue; + + 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]))) + error = GIT_ENOTFOUND; + } + + if (error) + git__free((void *)strs); + else + *out = strs; + + return error; +} + +int git_filter_list_new( + git_filter_list **out, git_repository *repo, git_filter_mode_t mode) +{ + git_filter_source src = { 0 }; + src.repo = repo; + src.path = NULL; + src.mode = mode; + return filter_list_new(out, &src); +} + +int git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode) +{ + int error = 0; + git_filter_list *fl = NULL; + git_filter_source src = { 0 }; + git_filter_entry *fe; + size_t idx; + git_filter_def *fdef; + + if (filter_registry_initialize() < 0) return -1; - for (i = 0; i < filters->length; ++i) { - git_filter *filter = git_vector_get(filters, i); - unsigned int dst = 1 - src; + src.repo = repo; + src.path = path; + src.mode = mode; + if (blob) + git_oid_cpy(&src.oid, git_blob_id(blob)); - git_buf_clear(dbuffer[dst]); + git_vector_foreach(&git__filter_registry->filters, idx, fdef) { + const char **values = NULL; + void *payload = NULL; + + if (!fdef || !fdef->filter) + continue; + + if (fdef->nattrs > 0) { + error = filter_list_check_attributes(&values, fdef, &src); + if (error == GIT_ENOTFOUND) { + error = 0; + continue; + } else if (error < 0) + break; + } + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + break; + + if (fdef->filter->check) + error = fdef->filter->check( + fdef->filter, &payload, &src, values); + + git__free((void *)values); + + if (error == GIT_PASSTHROUGH) + error = 0; + else if (error < 0) + break; + else { + if (!fl && (error = filter_list_new(&fl, &src)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GITERR_CHECK_ALLOC(fe); + fe->filter = fdef->filter; + fe->payload = payload; + } + } + + if (error && fl != NULL) { + git_array_clear(fl->filters); + git__free(fl); + fl = NULL; + } + + *filters = fl; + return error; +} + +void git_filter_list_free(git_filter_list *fl) +{ + uint32_t i; + + if (!fl) + return; + + for (i = 0; i < git_array_size(fl->filters); ++i) { + git_filter_entry *fe = git_array_get(fl->filters, i); + if (fe->filter->cleanup) + fe->filter->cleanup(fe->filter, fe->payload); + } + + git_array_clear(fl->filters); + git__free(fl); +} + +int git_filter_list_push( + git_filter_list *fl, git_filter *filter, void *payload) +{ + int error = 0; + size_t pos; + git_filter_def *fdef; + 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"); + return -1; + } + + fdef = git_vector_get(&git__filter_registry->filters, pos); + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GITERR_CHECK_ALLOC(fe); + fe->filter = filter; + fe->payload = payload; + + return 0; +} + +size_t git_filter_list_length(const git_filter_list *fl) +{ + return fl ? git_array_size(fl->filters) : 0; +} + +static int filter_list_out_buffer_from_raw( + git_buf *out, const void *ptr, size_t size) +{ + if (git_buf_is_allocated(out)) + git_buf_free(out); + + if (!size) { + git_buf_init(out, 0); + } else { + out->ptr = (char *)ptr; + out->asize = 0; + out->size = size; + } + + return 0; +} + +int git_filter_list_apply_to_data( + git_buf *tgt, git_filter_list *fl, git_buf *src) +{ + int error = 0; + uint32_t i; + git_buf *dbuffer[2], local = GIT_BUF_INIT; + unsigned int si = 0; + + if (!fl) + return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size); + + dbuffer[0] = src; + dbuffer[1] = tgt; + + /* if `src` buffer is reallocable, then use it, otherwise copy it */ + if (!git_buf_is_allocated(src)) { + if (git_buf_set(&local, src->ptr, src->size) < 0) + return -1; + dbuffer[0] = &local; + } + + for (i = 0; i < git_array_size(fl->filters); ++i) { + unsigned int di = 1 - si; + uint32_t fidx = (fl->source.mode == GIT_FILTER_TO_WORKTREE) ? + i : git_array_size(fl->filters) - 1 - i; + git_filter_entry *fe = git_array_get(fl->filters, fidx); + + dbuffer[di]->size = 0; /* Apply the filter from dbuffer[src] to the other buffer; * if the filtering is canceled by the user mid-filter, @@ -79,16 +605,72 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) * of the double buffering (so that the text goes through * cleanly). */ - if (filter->apply(filter, dbuffer[dst], dbuffer[src]) == 0) - src = dst; - if (git_buf_oom(dbuffer[dst])) - return -1; + error = fe->filter->apply( + fe->filter, &fe->payload, dbuffer[di], dbuffer[si], &fl->source); + + if (error == GIT_PASSTHROUGH) { + /* PASSTHROUGH means filter decided not to process the buffer */ + error = 0; + } else if (!error) { + git_buf_shorten(dbuffer[di], 0); /* force NUL termination */ + si = di; /* swap buffers */ + } else { + tgt->size = 0; + return error; + } } /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */ - if (src != 1) - git_buf_swap(dest, source); + if (si != 1) + git_buf_swap(dbuffer[0], dbuffer[1]); + + git_buf_free(&local); /* don't leak if we allocated locally */ return 0; } + +int git_filter_list_apply_to_file( + git_buf *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + int error; + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_buf abspath = GIT_BUF_INIT, raw = GIT_BUF_INIT; + + if (!(error = git_path_join_unrooted(&abspath, path, base, NULL)) && + !(error = git_futils_readbuffer(&raw, abspath.ptr))) + { + error = git_filter_list_apply_to_data(out, filters, &raw); + + git_buf_free(&raw); + } + + git_buf_free(&abspath); + return error; +} + +int git_filter_list_apply_to_blob( + git_buf *out, + git_filter_list *filters, + git_blob *blob) +{ + git_buf in = GIT_BUF_INIT; + git_off_t rawsize = git_blob_rawsize(blob); + + if (!git__is_sizet(rawsize)) { + giterr_set(GITERR_OS, "Blob is too large to filter"); + return -1; + } + + in.ptr = (char *)git_blob_rawcontent(blob); + in.asize = 0; + in.size = (size_t)rawsize; + + if (filters) + git_oid_cpy(&filters->source.oid, git_blob_id(blob)); + + return git_filter_list_apply_to_data(out, filters, &in); +} diff --git a/src/filter.h b/src/filter.h index 42a44ebdb..d0ace0f9a 100644 --- a/src/filter.h +++ b/src/filter.h @@ -8,19 +8,7 @@ #define INCLUDE_filter_h__ #include "common.h" -#include "buffer.h" -#include "git2/odb.h" -#include "git2/repository.h" - -typedef struct git_filter { - int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source); - void (*do_free)(struct git_filter *self); -} git_filter; - -typedef enum { - GIT_FILTER_TO_WORKTREE, - GIT_FILTER_TO_ODB -} git_filter_mode; +#include "git2/filter.h" typedef enum { GIT_CRLF_GUESS = -1, @@ -31,64 +19,13 @@ typedef enum { GIT_CRLF_AUTO, } git_crlf_t; -/* - * FILTER API - */ - -/* - * For any given path in the working directory, fill the `filters` - * array with the relevant filters that need to be applied. - * - * Mode is either `GIT_FILTER_TO_WORKTREE` if you need to load the - * filters that will be used when checking out a file to the working - * directory, or `GIT_FILTER_TO_ODB` for the filters used when writing - * a file to the ODB. - * - * @param filters Vector where to store all the loaded filters - * @param repo Repository object that contains `path` - * @param path Relative path of the file to be filtered - * @param mode Filtering direction (WT->ODB or ODB->WT) - * @return the number of filters loaded for the file (0 if the file - * doesn't need filtering), or a negative error code - */ -extern int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode); - -/* - * Apply one or more filters to a file. - * - * The file must have been loaded as a `git_buf` object. Both the `source` - * and `dest` buffers are owned by the caller and must be freed once - * they are no longer needed. - * - * NOTE: Because of the double-buffering schema, the `source` buffer that contains - * the original file may be tampered once the filtering is complete. Regardless, - * the `dest` buffer will always contain the final result of the filtering - * - * @param dest Buffer to store the result of the filtering - * @param source Buffer containing the document to filter - * @param filters A non-empty vector of filters as supplied by `git_filters_load` - * @return 0 on success, an error code otherwise - */ -extern int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters); - -/* - * Free the `filters` array generated by `git_filters_load`. - * - * Note that this frees both the array and its contents. The array will - * be clean/reusable after this call. - * - * @param filters A filters array as supplied by `git_filters_load` - */ -extern void git_filters_free(git_vector *filters); +extern void git_filter_free(git_filter *filter); /* * Available filters */ -/* Strip CRLF, from Worktree to ODB */ -extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); - -/* Add CRLF, from ODB to worktree */ -extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path); +extern git_filter *git_crlf_filter_new(void); +extern git_filter *git_ident_filter_new(void); #endif diff --git a/src/global.c b/src/global.c index 2d40ca2fc..7d39c6fa8 100644 --- a/src/global.c +++ b/src/global.c @@ -14,6 +14,28 @@ git_mutex git__mwindow_mutex; +#define MAX_SHUTDOWN_CB 8 + +git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; +git_atomic git__n_shutdown_callbacks; + +void git__on_shutdown(git_global_shutdown_fn callback) +{ + int count = git_atomic_inc(&git__n_shutdown_callbacks); + assert(count <= MAX_SHUTDOWN_CB); + git__shutdown_callbacks[count - 1] = callback; +} + +static void git__shutdown(void) +{ + int pos; + + while ((pos = git_atomic_dec(&git__n_shutdown_callbacks)) >= 0) { + if (git__shutdown_callbacks[pos]) + git__shutdown_callbacks[pos](); + } +} + /** * Handle the global state with TLS * @@ -51,47 +73,69 @@ git_mutex git__mwindow_mutex; #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; -static int _tls_init = 0; +static DWORD _mutex = 0; +static DWORD _n_inits = 0; -int git_threads_init(void) +static int synchronized_threads_init() { int error; - if (_tls_init) - return 0; - _tls_index = TlsAlloc(); if (git_mutex_init(&git__mwindow_mutex)) return -1; /* Initialize any other subsystems that have global state */ if ((error = git_hash_global_init()) >= 0) - _tls_init = 1; + error = git_futils_dirs_global_init(); - if (error == 0) - _tls_init = 1; - - GIT_MEMORY_BARRIER; + win32_pthread_initialize(); return error; } +int git_threads_init(void) +{ + int error = 0; + + /* Enter the lock */ + while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); } + + /* Only do work on a 0 -> 1 transition of the refcount */ + if (1 == ++_n_inits) + error = synchronized_threads_init(); + + /* Exit the lock */ + InterlockedExchange(&_mutex, 0); + + return error; +} + +static void synchronized_threads_shutdown() +{ + /* Shut down any subsystems that have global state */ + git__shutdown(); + TlsFree(_tls_index); + git_mutex_free(&git__mwindow_mutex); +} + void git_threads_shutdown(void) { - TlsFree(_tls_index); - _tls_init = 0; - git_mutex_free(&git__mwindow_mutex); + /* Enter the lock */ + while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); } - /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); + /* Only do work on a 1 -> 0 transition of the refcount */ + if (0 == --_n_inits) + synchronized_threads_shutdown(); + + /* Exit the lock */ + InterlockedExchange(&_mutex, 0); } git_global_st *git__global_state(void) { void *ptr; - assert(_tls_init); + assert(_n_inits); if ((ptr = TlsGetValue(_tls_index)) != NULL) return ptr; @@ -108,55 +152,58 @@ git_global_st *git__global_state(void) #elif defined(GIT_THREADS) && defined(_POSIX_THREADS) static pthread_key_t _tls_key; -static int _tls_init = 0; +static pthread_once_t _once_init = PTHREAD_ONCE_INIT; +static git_atomic git__n_inits; +int init_error = 0; static void cb__free_status(void *st) { git__free(st); } -int git_threads_init(void) +static void init_once(void) { - int error = 0; - - if (_tls_init) - return 0; - - if (git_mutex_init(&git__mwindow_mutex)) - return -1; + 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 ((error = git_hash_global_init()) >= 0) - _tls_init = 1; + if ((init_error = git_hash_global_init()) >= 0) + init_error = git_futils_dirs_global_init(); GIT_MEMORY_BARRIER; +} - return error; +int git_threads_init(void) +{ + pthread_once(&_once_init, init_once); + git_atomic_inc(&git__n_inits); + return init_error; } void git_threads_shutdown(void) { - if (_tls_init) { - void *ptr = pthread_getspecific(_tls_key); - pthread_setspecific(_tls_key, NULL); - git__free(ptr); - } + pthread_once_t new_once = PTHREAD_ONCE_INIT; - pthread_key_delete(_tls_key); - _tls_init = 0; - git_mutex_free(&git__mwindow_mutex); + if (git_atomic_dec(&git__n_inits) > 0) return; /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); + git__shutdown(); + + void *ptr = pthread_getspecific(_tls_key); + pthread_setspecific(_tls_key, NULL); + git__free(ptr); + + pthread_key_delete(_tls_key); + git_mutex_free(&git__mwindow_mutex); + _once_init = new_once; } git_global_st *git__global_state(void) { void *ptr; - assert(_tls_init); + assert(git__n_inits.val); if ((ptr = pthread_getspecific(_tls_key)) != NULL) return ptr; @@ -176,15 +223,14 @@ static git_global_st __state; int git_threads_init(void) { - /* noop */ + /* noop */ return 0; } void git_threads_shutdown(void) { /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); + git__shutdown(); } git_global_st *git__global_state(void) diff --git a/src/global.h b/src/global.h index badbc0883..778250376 100644 --- a/src/global.h +++ b/src/global.h @@ -21,4 +21,8 @@ extern git_mutex git__mwindow_mutex; #define GIT_GLOBAL (git__global_state()) +typedef void (*git_global_shutdown_fn)(void); + +extern void git__on_shutdown(git_global_shutdown_fn callback); + #endif diff --git a/src/hash.h b/src/hash.h index 5b848981f..c47f33549 100644 --- a/src/hash.h +++ b/src/hash.h @@ -13,8 +13,6 @@ typedef struct git_hash_prov git_hash_prov; typedef struct git_hash_ctx git_hash_ctx; int git_hash_global_init(void); -void git_hash_global_shutdown(void); - int git_hash_ctx_init(git_hash_ctx *ctx); void git_hash_ctx_cleanup(git_hash_ctx *ctx); diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h index 6b60c98c4..daeb1cda8 100644 --- a/src/hash/hash_generic.h +++ b/src/hash/hash_generic.h @@ -17,7 +17,6 @@ struct git_hash_ctx { }; #define git_hash_global_init() 0 -#define git_hash_global_shutdown() /* noop */ #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h index f83279a5a..9a55d472d 100644 --- a/src/hash/hash_openssl.h +++ b/src/hash/hash_openssl.h @@ -17,7 +17,6 @@ struct git_hash_ctx { }; #define git_hash_global_init() 0 -#define git_hash_global_shutdown() /* noop */ #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c index 43d54ca6d..bb2231364 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/hash_win32.c @@ -20,33 +20,16 @@ static struct git_hash_prov hash_prov = {0}; /* Initialize CNG, if available */ GIT_INLINE(int) hash_cng_prov_init(void) { - OSVERSIONINFOEX version_test = {0}; - DWORD version_test_mask; - DWORDLONG version_condition_mask = 0; char dll_path[MAX_PATH]; DWORD dll_path_len, size_len; - return -1; - /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - version_test.dwMajorVersion = 6; - version_test.dwMinorVersion = 0; - version_test.wServicePackMajor = 1; - version_test.wServicePackMinor = 0; - - version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); - - VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + if (!git_has_win32_version(6, 0, 1)) return -1; /* Load bcrypt.dll explicitly from the system directory */ - if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || + dll_path_len > MAX_PATH || StringCchCat(dll_path, MAX_PATH, "\\") < 0 || StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) @@ -106,7 +89,15 @@ GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) hash_prov.type = INVALID; } -int git_hash_global_init() +static void git_hash_global_shutdown(void) +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +int git_hash_global_init(void) { int error = 0; @@ -116,15 +107,9 @@ int git_hash_global_init() if ((error = hash_cng_prov_init()) < 0) error = hash_cryptoapi_prov_init(); - return error; -} + git__on_shutdown(git_hash_global_shutdown); -void git_hash_global_shutdown() -{ - if (hash_prov.type == CNG) - hash_cng_prov_shutdown(); - else if(hash_prov.type == CRYPTOAPI) - hash_cryptoapi_prov_shutdown(); + return error; } /* CryptoAPI: available in Windows XP and newer */ diff --git a/src/hashsig.c b/src/hashsig.c index ab8d8b3f0..109f966ba 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -13,12 +13,15 @@ typedef uint64_t hashsig_state; #define HASHSIG_SCALE 100 -#define HASHSIG_HASH_WINDOW 32 -#define HASHSIG_HASH_START 0 +#define HASHSIG_MAX_RUN 80 +#define HASHSIG_HASH_START 0x012345678ABCDEF0LL #define HASHSIG_HASH_SHIFT 5 -#define HASHSIG_HASH_MASK 0x7FFFFFFF + +#define HASHSIG_HASH_MIX(S,CH) \ + (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) #define HASHSIG_HEAP_SIZE ((1 << 7) - 1) +#define HASHSIG_HEAP_MIN_SIZE 4 typedef int (*hashsig_cmp)(const void *a, const void *b, void *); @@ -28,14 +31,6 @@ typedef struct { hashsig_t values[HASHSIG_HEAP_SIZE]; } hashsig_heap; -typedef struct { - hashsig_state state, shift_n; - char window[HASHSIG_HASH_WINDOW]; - int win_len, win_pos, saw_lf; -} hashsig_in_progress; - -#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 } - struct git_hashsig { hashsig_heap mins; hashsig_heap maxs; @@ -43,8 +38,8 @@ struct git_hashsig { int considered; }; -#define HEAP_LCHILD_OF(I) (((I)*2)+1) -#define HEAP_RCHILD_OF(I) (((I)*2)+2) +#define HEAP_LCHILD_OF(I) (((I)<<1)+1) +#define HEAP_RCHILD_OF(I) (((I)<<1)+2) #define HEAP_PARENT_OF(I) (((I)-1)>>1) static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) @@ -115,134 +110,109 @@ static void hashsig_heap_sort(hashsig_heap *h) static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) { - /* if heap is full, pop top if new element should replace it */ - if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) { - h->size--; - h->values[0] = h->values[h->size]; - hashsig_heap_down(h, 0); - } - /* if heap is not full, insert new element */ if (h->size < h->asize) { h->values[h->size++] = val; hashsig_heap_up(h, h->size - 1); } + + /* if heap is full, pop top if new element should replace it */ + else if (h->cmp(&val, &h->values[0], NULL) > 0) { + h->size--; + h->values[0] = h->values[h->size]; + hashsig_heap_down(h, 0); + } + } -GIT_INLINE(bool) hashsig_include_char( - char ch, git_hashsig_option_t opt, int *saw_lf) +typedef struct { + int use_ignores; + uint8_t ignore_ch[256]; +} hashsig_in_progress; + +static void hashsig_in_progress_init( + hashsig_in_progress *prog, git_hashsig *sig) { - if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch)) - return false; + int i; - if (opt & GIT_HASHSIG_SMART_WHITESPACE) { - if (ch == '\r' || (*saw_lf && git__isspace(ch))) - return false; - - *saw_lf = (ch == '\n'); + switch (sig->opt) { + case GIT_HASHSIG_IGNORE_WHITESPACE: + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace_nonlf(i); + prog->use_ignores = 1; + break; + case GIT_HASHSIG_SMART_WHITESPACE: + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace(i); + prog->use_ignores = 1; + break; + default: + memset(prog, 0, sizeof(*prog)); + break; } - - return true; } -static void hashsig_initial_window( - git_hashsig *sig, - const char **data, - size_t size, - hashsig_in_progress *prog) -{ - hashsig_state state, shift_n; - int win_len; - const char *scan, *end; - - /* init until we have processed at least HASHSIG_HASH_WINDOW data */ - - if (prog->win_len >= HASHSIG_HASH_WINDOW) - return; - - state = prog->state; - win_len = prog->win_len; - shift_n = prog->shift_n; - - scan = *data; - end = scan + size; - - while (scan < end && win_len < HASHSIG_HASH_WINDOW) { - char ch = *scan++; - - if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf)) - continue; - - state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK; - - if (!win_len) - shift_n = 1; - else - shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; - - prog->window[win_len++] = ch; - } - - /* insert initial hash if we just finished */ - - if (win_len == HASHSIG_HASH_WINDOW) { - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - sig->considered = 1; - } - - prog->state = state; - prog->win_len = win_len; - prog->shift_n = shift_n; - - *data = scan; -} +#define HASHSIG_IN_PROGRESS_INIT { 1 } static int hashsig_add_hashes( git_hashsig *sig, - const char *data, + const uint8_t *data, size_t size, hashsig_in_progress *prog) { - const char *scan = data, *end = data + size; - hashsig_state state, shift_n, rmv; + const uint8_t *scan = data, *end = data + size; + hashsig_state state = HASHSIG_HASH_START; + int use_ignores = prog->use_ignores, len; + uint8_t ch; - if (prog->win_len < HASHSIG_HASH_WINDOW) - hashsig_initial_window(sig, &scan, size, prog); + while (scan < end) { + state = HASHSIG_HASH_START; - state = prog->state; - shift_n = prog->shift_n; + for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { + ch = *scan; - /* advance window, adding new chars and removing old */ + if (use_ignores) + for (; scan < end && git__isspace_nonlf(ch); ch = *scan) + ++scan; + else if (sig->opt != GIT_HASHSIG_NORMAL) + for (; scan < end && ch == '\r'; ch = *scan) + ++scan; - for (; scan < end; ++scan) { - char ch = *scan; + /* peek at next character to decide what to do next */ + if (sig->opt == GIT_HASHSIG_SMART_WHITESPACE) + use_ignores = (ch == '\n'); - if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf)) - continue; + if (scan >= end) + break; + ++scan; - rmv = shift_n * prog->window[prog->win_pos]; + /* check run terminator */ + if (ch == '\n' || ch == '\0') + break; - state = (state - rmv) & HASHSIG_HASH_MASK; - state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK; - state = (state + ch) & HASHSIG_HASH_MASK; + ++len; + HASHSIG_HASH_MIX(state, ch); + } - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - sig->considered++; + if (len > 0) { + hashsig_heap_insert(&sig->mins, (hashsig_t)state); + hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - prog->window[prog->win_pos] = ch; - prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW; + sig->considered++; + + while (scan < end && (*scan == '\n' || !*scan)) + ++scan; + } } - prog->state = state; + prog->use_ignores = use_ignores; return 0; } static int hashsig_finalize_hashes(git_hashsig *sig) { - if (sig->mins.size < HASHSIG_HEAP_SIZE) { + if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE) { giterr_set(GITERR_INVALID, "File too small for similarity signature calculation"); return GIT_EBUFS; @@ -274,11 +244,13 @@ int git_hashsig_create( git_hashsig_option_t opts) { int error; - hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; + hashsig_in_progress prog; git_hashsig *sig = hashsig_alloc(opts); GITERR_CHECK_ALLOC(sig); - error = hashsig_add_hashes(sig, buf, buflen, &prog); + hashsig_in_progress_init(&prog, sig); + + error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); if (!error) error = hashsig_finalize_hashes(sig); @@ -296,10 +268,10 @@ int git_hashsig_create_fromfile( const char *path, git_hashsig_option_t opts) { - char buf[4096]; + uint8_t buf[0x1000]; ssize_t buflen = 0; int error = 0, fd; - hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT; + hashsig_in_progress prog; git_hashsig *sig = hashsig_alloc(opts); GITERR_CHECK_ALLOC(sig); @@ -308,6 +280,8 @@ int git_hashsig_create_fromfile( return fd; } + hashsig_in_progress_init(&prog, sig); + while (!error) { if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { if ((error = (int)buflen) < 0) @@ -362,6 +336,12 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) { - return (hashsig_heap_compare(&a->mins, &b->mins) + - hashsig_heap_compare(&a->maxs, &b->maxs)) / 2; + /* if we have fewer than the maximum number of elements, then just use + * one array since the two arrays will be the same + */ + if (a->mins.size < HASHSIG_HEAP_SIZE) + return hashsig_heap_compare(&a->mins, &b->mins); + else + return (hashsig_heap_compare(&a->mins, &b->mins) + + hashsig_heap_compare(&a->maxs, &b->maxs)) / 2; } diff --git a/src/ident.c b/src/ident.c new file mode 100644 index 000000000..51630879d --- /dev/null +++ b/src/ident.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/sys/filter.h" +#include "filter.h" +#include "buffer.h" +#include "buf_text.h" + +static int ident_find_id( + const char **id_start, const char **id_end, const char *start, size_t len) +{ + const char *end = start + len, *found = NULL; + + while (len > 3 && (found = memchr(start, '$', len)) != NULL) { + size_t remaining = (size_t)(end - found) - 1; + if (remaining < 3) + return GIT_ENOTFOUND; + + start = found + 1; + len = remaining; + + if (start[0] == 'I' && start[1] == 'd') + break; + } + + if (len < 3 || !found) + return GIT_ENOTFOUND; + *id_start = found; + + if ((found = memchr(start + 2, '$', len - 2)) == NULL) + return GIT_ENOTFOUND; + + *id_end = found + 1; + return 0; +} + +static int ident_insert_id( + git_buf *to, const git_buf *from, const git_filter_source *src) +{ + char oid[GIT_OID_HEXSZ+1]; + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + /* replace $Id$ with blob id */ + + if (!git_filter_source_id(src)) + return GIT_PASSTHROUGH; + + git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 5 /* "$Id: " */ + GIT_OID_HEXSZ + 1 /* "$" */ + + (size_t)(from_end - id_end); + + if (git_buf_grow(to, need_size) < 0) + return -1; + + git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(to, "$Id: ", 5); + git_buf_put(to, oid, GIT_OID_HEXSZ); + git_buf_putc(to, '$'); + git_buf_put(to, id_end, (size_t)(from_end - id_end)); + + return git_buf_oom(to) ? -1 : 0; +} + +static int ident_remove_id( + git_buf *to, const git_buf *from) +{ + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 4 /* "$Id$" */ + (size_t)(from_end - id_end); + + if (git_buf_grow(to, need_size) < 0) + return -1; + + git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(to, "$Id$", 4); + git_buf_put(to, id_end, (size_t)(from_end - id_end)); + + return git_buf_oom(to) ? -1 : 0; +} + +static int ident_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *src) +{ + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* Don't filter binary files */ + if (git_buf_text_is_binary(from)) + return GIT_PASSTHROUGH; + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return ident_insert_id(to, from, src); + else + return ident_remove_id(to, from); +} + +git_filter *git_ident_filter_new(void) +{ + git_filter *f = git__calloc(1, sizeof(git_filter)); + + f->version = GIT_FILTER_VERSION; + f->attributes = "+ident"; /* apply to files with ident attribute set */ + f->shutdown = git_filter_free; + f->apply = ident_apply; + + return f; +} diff --git a/src/ignore.c b/src/ignore.c index cc90b0c61..27d7c7ec4 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,4 +1,5 @@ #include "git2/ignore.h" +#include "common.h" #include "ignore.h" #include "attr.h" #include "path.h" @@ -37,7 +38,7 @@ static int parse_ignore_file( GITERR_CHECK_ALLOC(match); } - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; if (!(error = git_attr_fnmatch__parse( match, ignores->pool, context, &scan))) @@ -159,17 +160,36 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir) { if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) return -1; - else - return push_ignore_file( - ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); + + return push_ignore_file( + ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); } int git_ignore__pop_dir(git_ignores *ign) { if (ign->ign_path.length > 0) { git_attr_file *file = git_vector_last(&ign->ign_path); - if (git__suffixcmp(ign->dir.ptr, file->key + 2) == 0) + const char *start, *end, *scan; + size_t keylen; + + /* - ign->dir looks something like "a/b" (or "a/b/c/d") + * - file->key looks something like "0#a/b/.gitignore + * + * We are popping the last directory off ign->dir. We also want to + * remove the file from the vector if the directory part of the key + * matches the ign->dir path. We need to test if the "a/b" part of + * the file key matches the path we are about to pop. + */ + + for (start = end = scan = &file->key[2]; *scan; ++scan) + if (*scan == '/') + end = scan; /* point 'end' to last '/' in key */ + keylen = (end - start) + 1; + + if (ign->dir.size >= keylen && + !memcmp(ign->dir.ptr + ign->dir.size - keylen, start, keylen)) git_vector_pop(&ign->ign_path); + git_buf_rtruncate_at_char(&ign->dir, '/'); } return 0; @@ -298,12 +318,9 @@ int git_ignore_path_is_ignored( path.full.size = (tail - path.full.ptr); path.is_dir = (tail == end) ? full_is_dir : true; - /* update ignores for new path fragment */ - if (path.basename == path.path) - error = git_ignore__for_path(repo, path.path, &ignores); - else - error = git_ignore__push_dir(&ignores, path.basename); - if (error < 0) + /* initialize ignores the first time through */ + if (path.basename == path.path && + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) break; /* first process builtins - success means path was found */ @@ -327,6 +344,10 @@ int git_ignore_path_is_ignored( if (tail == end) break; + /* now add this directory to list of ignores */ + if ((error = git_ignore__push_dir(&ignores, path.path)) < 0) + break; + /* reinstate divider in path */ *tail = '/'; while (*tail == '/') tail++; diff --git a/src/ignore.h b/src/ignore.h index cc114b001..851c824bf 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -24,14 +24,15 @@ */ typedef struct { git_repository *repo; - git_buf dir; + git_buf dir; /* current directory reflected in ign_path */ git_attr_file *ign_internal; git_vector ign_path; git_vector ign_global; int ignore_case; } git_ignores; -extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign); +extern int git_ignore__for_path( + git_repository *repo, const char *path, git_ignores *ign); extern int git_ignore__push_dir(git_ignores *ign, const char *dir); diff --git a/src/index.c b/src/index.c index 1d46779bf..09e7b2346 100644 --- a/src/index.c +++ b/src/index.c @@ -16,6 +16,7 @@ #include "iterator.h" #include "pathspec.h" #include "ignore.h" +#include "blob.h" #include "git2/odb.h" #include "git2/oid.h" @@ -101,8 +102,6 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) static bool is_index_extended(git_index *index); static int write_index(git_index *index, git_filebuf *file); -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage); - static void index_entry_free(git_index_entry *entry); static void index_entry_reuc_free(git_index_reuc_entry *reuc); @@ -114,7 +113,7 @@ static int index_srch(const void *key, const void *array_member) ret = strcmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -128,7 +127,7 @@ static int index_isrch(const void *key, const void *array_member) ret = strcasecmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -261,6 +260,22 @@ static int reuc_icmp(const void *a, const void *b) return strcasecmp(info_a->path, info_b->path); } +static void index_entry_reuc_free(git_index_reuc_entry *reuc) +{ + if (!reuc) + return; + git__free(reuc->path); + git__free(reuc); +} + +static void index_entry_free(git_index_entry *entry) +{ + if (!entry) + return; + git__free(entry->path); + git__free(entry); +} + static unsigned int index_create_mode(unsigned int mode) { if (S_ISLNK(mode)) @@ -269,7 +284,7 @@ static unsigned int index_create_mode(unsigned int mode) if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) return (S_IFLNK | S_IFDIR); - return S_IFREG | ((mode & 0100) ? 0755 : 0644); + return S_IFREG | GIT_PERMS_CANONICAL(mode); } static unsigned int index_merge_mode( @@ -306,6 +321,7 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) int git_index_open(git_index **index_out, const char *index_path) { git_index *index; + int error; assert(index_out); @@ -331,10 +347,15 @@ int git_index_open(git_index **index_out, const char *index_path) index->entries_search_path = index_srch_path; index->reuc_search = reuc_srch; + if ((index_path != NULL) && ((error = git_index_read(index, true)) < 0)) { + git_index_free(index); + return error; + } + *index_out = index; GIT_REFCOUNT_INC(index); - return (index_path != NULL) ? git_index_read(index) : 0; + return 0; } int git_index_new(git_index **out) @@ -367,11 +388,8 @@ static void index_entries_free(git_vector *entries) { size_t i; - for (i = 0; i < entries->length; ++i) { - git_index_entry *e = git_vector_get(entries, i); - git__free(e->path); - git__free(e); - } + for (i = 0; i < entries->length; ++i) + index_entry_free(git__swap(entries->contents[i], NULL)); git_vector_clear(entries); } @@ -398,7 +416,7 @@ static int create_index_error(int error, const char *msg) int git_index_set_caps(git_index *index, unsigned int caps) { - int old_ignore_case; + unsigned int old_ignore_case; assert(index); @@ -426,7 +444,7 @@ int git_index_set_caps(git_index *index, unsigned int caps) } if (old_ignore_case != index->ignore_case) { - git_index__set_ignore_case(index, index->ignore_case); + git_index__set_ignore_case(index, (bool)index->ignore_case); } return 0; @@ -439,24 +457,26 @@ unsigned int git_index_caps(const git_index *index) (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0)); } -int git_index_read(git_index *index) +int git_index_read(git_index *index, int force) { int error = 0, updated; git_buf buffer = GIT_BUF_INIT; - git_futils_filestamp stamp = {0}; + git_futils_filestamp stamp = index->stamp; if (!index->index_file_path) return create_index_error(-1, "Failed to read index: The index is in-memory only"); - if (!index->on_disk || git_path_exists(index->index_file_path) == false) { - git_index_clear(index); - index->on_disk = 0; + index->on_disk = git_path_exists(index->index_file_path); + + if (!index->on_disk) { + if (force) + git_index_clear(index); return 0; } updated = git_futils_filestamp_check(&stamp, index->index_file_path); - if (updated <= 0) + if (updated < 0 || (!updated && !force)) return updated; error = git_futils_readbuffer(&buffer, index->index_file_path); @@ -486,15 +506,19 @@ int git_index_write(git_index *index) git_vector_sort(&index->reuc); if ((error = git_filebuf_open( - &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) + &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) { + if (error == GIT_ELOCKED) + giterr_set(GITERR_INDEX, "The index is locked. This might be due to a concurrrent or crashed process"); + return error; + } if ((error = write_index(index, &file)) < 0) { git_filebuf_cleanup(&file); return error; } - if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0) + if ((error = git_filebuf_commit(&file)) < 0) return error; error = git_futils_filestamp_check(&index->stamp, index->index_file_path); @@ -505,6 +529,12 @@ int git_index_write(git_index *index) return 0; } +const char * git_index_path(git_index *index) +{ + assert(index); + return index->index_file_path; +} + int git_index_write_tree(git_oid *oid, git_index *index) { git_repository *repo; @@ -549,7 +579,7 @@ const git_index_entry *git_index_get_bypath( git_vector_sort(&index->entries); - if (index_find(&pos, index, path, stage) < 0) { + if (git_index__find(&pos, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s", path); return NULL; } @@ -557,7 +587,8 @@ const git_index_entry *git_index_get_bypath( return git_index_get_byindex(index, pos); } -void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st) +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; @@ -565,7 +596,8 @@ void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st) /* entry->ctime.nanoseconds = st->st_ctimensec; */ entry->dev = st->st_rdev; entry->ino = st->st_ino; - entry->mode = index_create_mode(st->st_mode); + entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? + index_create_mode(0666) : index_create_mode(st->st_mode); entry->uid = st->st_uid; entry->gid = st->st_gid; entry->file_size = st->st_size; @@ -587,48 +619,29 @@ int git_index_entry__cmp_icase(const void *a, const void *b) return strcasecmp(entry_a->path, entry_b->path); } -static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path) +static int index_entry_init( + git_index_entry **entry_out, git_index *index, const char *rel_path) { + int error = 0; git_index_entry *entry = NULL; struct stat st; git_oid oid; - const char *workdir; - git_buf full_path = GIT_BUF_INIT; - int error; if (INDEX_OWNER(index) == NULL) return create_index_error(-1, "Could not initialize index entry. " "Index is not backed up by an existing repository."); - workdir = git_repository_workdir(INDEX_OWNER(index)); - - if (!workdir) - return create_index_error(GIT_EBAREREPO, - "Could not initialize index entry. Repository is bare"); - - if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0) - return error; - - if ((error = git_path_lstat(full_path.ptr, &st)) < 0) { - git_buf_free(&full_path); - return error; - } - - git_buf_free(&full_path); /* done with full path */ - - /* There is no need to validate the rel_path here, since it will be - * immediately validated by the call to git_blob_create_fromfile. - */ - - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0) + /* write the blob to disk and get the oid and stat info */ + error = git_blob__create_from_paths( + &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); + if (error < 0) return error; entry = git__calloc(1, sizeof(git_index_entry)); GITERR_CHECK_ALLOC(entry); - git_index_entry__init_from_stat(entry, &st); + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); entry->oid = oid; entry->path = git__strdup(rel_path); @@ -670,15 +683,6 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, return 0; } -static void index_entry_reuc_free(git_index_reuc_entry *reuc) -{ - if (!reuc) - return; - - git__free(reuc->path); - git__free(reuc); -} - static git_index_entry *index_entry_dup(const git_index_entry *source_entry) { git_index_entry *entry; @@ -697,14 +701,6 @@ static git_index_entry *index_entry_dup(const git_index_entry *source_entry) return entry; } -static void index_entry_free(git_index_entry *entry) -{ - if (!entry) - return; - git__free(entry->path); - git__free(entry); -} - static int index_insert(git_index *index, git_index_entry *entry, int replace) { size_t path_length, position; @@ -723,7 +719,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) entry->flags |= GIT_IDXENTRY_NAMEMASK; /* look if an entry with this path already exists */ - if (!index_find(&position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { + if (!git_index__find( + &position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { existing = (git_index_entry **)&index->entries.contents[position]; /* update filemode to existing values if stat is not trusted */ @@ -835,7 +832,7 @@ int git_index_remove(git_index *index, const char *path, int stage) git_vector_sort(&index->entries); - if (index_find(&position, index, path, stage) < 0) { + if (git_index__find(&position, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d", path, stage); return GIT_ENOTFOUND; @@ -891,7 +888,8 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) return error; } -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage) +int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage) { struct entry_srch_key srch_key; @@ -900,7 +898,8 @@ static int index_find(size_t *at_pos, git_index *index, const char *path, int st srch_key.path = path; srch_key.stage = stage; - return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key); + return git_vector_bsearch2( + at_pos, &index->entries, index->entries_search, &srch_key); } int git_index_find(size_t *at_pos, git_index *index, const char *path) @@ -1357,14 +1356,11 @@ int git_index_reuc_remove(git_index *index, size_t position) void git_index_reuc_clear(git_index *index) { size_t i; - git_index_reuc_entry *reuc; assert(index); - git_vector_foreach(&index->reuc, i, reuc) { - git__free(reuc->path); - git__free(reuc); - } + for (i = 0; i < index->reuc.length; ++i) + index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL)); git_vector_clear(&index->reuc); } @@ -1389,7 +1385,7 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) while (size) { git_index_reuc_entry *lost; - len = strlen(buffer) + 1; + len = p_strnlen(buffer, size) + 1; if (size <= len) return index_error_invalid("reading reuc entries"); @@ -1409,14 +1405,18 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || - (unsigned)tmp > UINT_MAX) + (unsigned)tmp > UINT_MAX) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); + } lost->mode[i] = tmp; len = (endptr + 1) - buffer; - if (size <= len) + if (size <= len) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); + } size -= len; buffer += len; @@ -1426,8 +1426,10 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) for (i = 0; i < 3; i++) { if (!lost->mode[i]) continue; - if (size < 20) + if (size < 20) { + index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry oid"); + } git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); size -= 20; @@ -1456,7 +1458,7 @@ static int read_conflict_names(git_index *index, const char *buffer, size_t size return -1; #define read_conflict_name(ptr) \ - len = strlen(buffer) + 1; \ + len = p_strnlen(buffer, size) + 1; \ if (size < len) \ return index_error_invalid("reading conflict name entries"); \ \ @@ -1583,7 +1585,8 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer total_size = dest.extension_size + sizeof(struct index_extension); - if (buffer_size < total_size || + if (dest.extension_size > total_size || + buffer_size < total_size || buffer_size - total_size < INDEX_FOOTER_SIZE) return 0; @@ -1958,8 +1961,9 @@ int git_index_entry_stage(const git_index_entry *entry) } typedef struct read_tree_data { - git_index *index; git_vector *old_entries; + git_vector *new_entries; + git_vector_cmp entries_search; } read_tree_data; static int read_tree_cb( @@ -1990,7 +1994,7 @@ static int read_tree_cb( skey.stage = 0; if (!git_vector_bsearch2( - &pos, data->old_entries, data->index->entries_search, &skey) && + &pos, data->old_entries, data->entries_search, &skey) && (old_entry = git_vector_get(data->old_entries, pos)) != NULL && entry->mode == old_entry->mode && git_oid_equal(&entry->oid, &old_entry->oid)) @@ -2008,7 +2012,7 @@ static int read_tree_cb( entry->path = git_buf_detach(&path); git_buf_free(&path); - if (git_vector_insert(&data->index->entries, entry) < 0) { + if (git_vector_insert(data->new_entries, entry) < 0) { index_entry_free(entry); return -1; } @@ -2022,22 +2026,22 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector entries = GIT_VECTOR_INIT; read_tree_data data; + git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ + + data.old_entries = &index->entries; + data.new_entries = &entries; + data.entries_search = index->entries_search; + git_vector_sort(&index->entries); - git_vector_set_cmp(&entries, index->entries._cmp); - git_vector_swap(&entries, &index->entries); - - git_index_clear(index); - - data.index = index; - data.old_entries = &entries; - error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); - index_entries_free(&entries); - git_vector_free(&entries); + git_vector_sort(&entries); - git_vector_sort(&index->entries); + git_index_clear(index); + + git_vector_swap(&entries, &index->entries); + git_vector_free(&entries); return error; } @@ -2059,7 +2063,7 @@ int git_index_add_all( git_iterator *wditer = NULL; const git_index_entry *wd = NULL; git_index_entry *entry; - git_pathspec_context ps; + git_pathspec ps; const char *match; size_t existing; bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; @@ -2080,7 +2084,7 @@ int git_index_add_all( if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0) return -1; - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; /* optionally check that pathspec doesn't mention any ignored files */ @@ -2097,14 +2101,14 @@ int git_index_add_all( while (!(error = git_iterator_advance(&wd, wditer))) { /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match)) + if (!git_pathspec__match( + &ps.pathspec, wd->path, no_fnmatch, (bool)ignorecase, &match, NULL)) continue; /* skip ignored items that are not already in the index */ if ((flags & GIT_INDEX_ADD_FORCE) == 0 && git_iterator_current_is_ignored(wditer) && - index_find(&existing, index, wd->path, 0) < 0) + git_index__find(&existing, index, wd->path, 0) < 0) continue; /* issue notification callback if requested */ @@ -2154,7 +2158,7 @@ int git_index_add_all( cleanup: git_iterator_free(wditer); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } @@ -2174,13 +2178,13 @@ static int index_apply_to_all( { int error = 0; size_t i; - git_pathspec_context ps; + git_pathspec ps; const char *match; git_buf path = GIT_BUF_INIT; assert(index); - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; git_vector_sort(&index->entries); @@ -2189,8 +2193,9 @@ static int index_apply_to_all( git_index_entry *entry = git_vector_get(&index->entries, i); /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, entry->path, false, index->ignore_case, &match)) + if (!git_pathspec__match( + &ps.pathspec, entry->path, false, (bool)index->ignore_case, + &match, NULL)) continue; /* issue notification callback if requested */ @@ -2237,7 +2242,7 @@ static int index_apply_to_all( } git_buf_free(&path); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } diff --git a/src/index.h b/src/index.h index a59107a7b..4c448fabf 100644 --- a/src/index.h +++ b/src/index.h @@ -47,13 +47,17 @@ struct git_index_conflict_iterator { size_t cur; }; -extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); +extern void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode); extern size_t git_index__prefix_position(git_index *index, const char *path); extern int git_index_entry__cmp(const void *a, const void *b); extern int git_index_entry__cmp_icase(const void *a, const void *b); +extern int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage); + extern void git_index__set_ignore_case(git_index *index, bool ignore_case); #endif diff --git a/src/indexer.c b/src/indexer.c index 1b5339f23..df1ce7cfb 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -18,6 +18,7 @@ #include "filebuf.h" #include "oid.h" #include "oidmap.h" +#include "compress.h" #define UINT31_MAX (0x7FFFFFFF) @@ -28,13 +29,15 @@ struct entry { uint64_t offset_long; }; -struct git_indexer_stream { +struct git_indexer { unsigned int parsed_header :1, opened_pack :1, have_stream :1, have_delta :1; + struct git_pack_header hdr; struct git_pack_file *pack; git_filebuf pack_file; + unsigned int mode; git_off_t off; git_off_t entry_start; git_packfile_stream stream; @@ -47,13 +50,21 @@ struct git_indexer_stream { git_transfer_progress_callback progress_cb; void *progress_payload; char objbuf[8*1024]; + + /* Needed to look up objects which we want to inject to fix a thin pack */ + git_odb *odb; + + /* Fields for calculating the packfile trailer (hash of everything before it) */ + char inbuf[GIT_OID_RAWSZ]; + size_t inbuf_len; + git_hash_ctx trailer; }; struct delta_info { git_off_t delta_off; }; -const git_oid *git_indexer_stream_hash(const git_indexer_stream *idx) +const git_oid *git_indexer_hash(const git_indexer *idx) { return &idx->hash; } @@ -106,28 +117,34 @@ static int objects_cmp(const void *a, const void *b) return git_oid__cmp(&entrya->oid, &entryb->oid); } -int git_indexer_stream_new( - git_indexer_stream **out, +int git_indexer_new( + git_indexer **out, const char *prefix, + unsigned int mode, + git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_payload) { - git_indexer_stream *idx; + git_indexer *idx; git_buf path = GIT_BUF_INIT; static const char suff[] = "/pack"; int error; - idx = git__calloc(1, sizeof(git_indexer_stream)); + idx = git__calloc(1, sizeof(git_indexer)); GITERR_CHECK_ALLOC(idx); + idx->odb = odb; idx->progress_cb = progress_cb; idx->progress_payload = progress_payload; + idx->mode = mode ? mode : GIT_PACK_FILE_MODE; + git_hash_ctx_init(&idx->trailer); error = git_buf_joinpath(&path, prefix, suff); if (error < 0) goto cleanup; error = git_filebuf_open(&idx->pack_file, path.ptr, - GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER); + GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER, + idx->mode); git_buf_free(&path); if (error < 0) goto cleanup; @@ -143,7 +160,7 @@ cleanup: } /* Try to store the delta so we can try to resolve it later */ -static int store_delta(git_indexer_stream *idx) +static int store_delta(git_indexer *idx) { struct delta_info *delta; @@ -166,7 +183,7 @@ static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type) git_hash_update(ctx, buffer, hdrlen); } -static int hash_object_stream(git_indexer_stream *idx, git_packfile_stream *stream) +static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) { ssize_t read; @@ -186,7 +203,7 @@ static int hash_object_stream(git_indexer_stream *idx, git_packfile_stream *stre } /* In order to create the packfile stream, we need to skip over the delta base description */ -static int advance_delta_offset(git_indexer_stream *idx, git_otype type) +static int advance_delta_offset(git_indexer *idx, git_otype type) { git_mwindow *w = NULL; @@ -205,7 +222,7 @@ static int advance_delta_offset(git_indexer_stream *idx, git_otype type) } /* Read from the stream and discard any output */ -static int read_object_stream(git_indexer_stream *idx, git_packfile_stream *stream) +static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) { ssize_t read; @@ -245,7 +262,7 @@ static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, return 0; } -static int store_object(git_indexer_stream *idx) +static int store_object(git_indexer *idx) { int i, error; khiter_t k; @@ -303,17 +320,10 @@ on_error: return -1; } -static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) +static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, git_off_t entry_start) { int i, error; khiter_t k; - git_oid oid; - size_t entry_size; - struct entry *entry; - struct git_pack_entry *pentry; - - entry = git__calloc(1, sizeof(*entry)); - GITERR_CHECK_ALLOC(entry); if (entry_start > UINT31_MAX) { entry->offset = UINT32_MAX; @@ -322,25 +332,43 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent entry->offset = (uint32_t)entry_start; } - /* FIXME: Parse the object instead of hashing it */ + pentry->offset = entry_start; + k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error); + if (!error) + return -1; + + kh_value(idx->pack->idx_cache, k) = pentry; + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + return -1; + + for (i = entry->oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; +} + +static int hash_and_save(git_indexer *idx, git_rawobj *obj, git_off_t entry_start) +{ + git_oid oid; + size_t entry_size; + struct entry *entry; + struct git_pack_entry *pentry; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + if (git_odb__hashobj(&oid, obj) < 0) { giterr_set(GITERR_INDEXER, "Failed to hash object"); - return -1; + goto on_error; } pentry = git__calloc(1, sizeof(struct git_pack_entry)); GITERR_CHECK_ALLOC(pentry); git_oid_cpy(&pentry->sha1, &oid); - pentry->offset = entry_start; - k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error); - if (!error) { - git__free(pentry); - goto on_error; - } - - kh_value(idx->pack->idx_cache, k) = pentry; - git_oid_cpy(&entry->oid, &oid); entry->crc = crc32(0L, Z_NULL, 0); @@ -348,15 +376,7 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) goto on_error; - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - goto on_error; - - for (i = oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; + return save_entry(idx, entry, pentry, entry_start); on_error: git__free(entry); @@ -364,17 +384,54 @@ on_error: return -1; } -static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats) +static int do_progress_callback(git_indexer *idx, git_transfer_progress *stats) { if (!idx->progress_cb) return 0; return idx->progress_cb(stats, idx->progress_payload); } -int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats) +/* Hash everything but the last 20B of input */ +static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) +{ + size_t to_expell, to_keep; + + if (size == 0) + return; + + /* Easy case, dump the buffer and the data minus the last 20 bytes */ + if (size >= GIT_OID_RAWSZ) { + git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); + git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); + + data += size - GIT_OID_RAWSZ; + memcpy(idx->inbuf, data, GIT_OID_RAWSZ); + idx->inbuf_len = GIT_OID_RAWSZ; + return; + } + + /* We can just append */ + if (idx->inbuf_len + size <= GIT_OID_RAWSZ) { + memcpy(idx->inbuf + idx->inbuf_len, data, size); + idx->inbuf_len += size; + return; + } + + /* We need to partially drain the buffer and then append */ + to_keep = GIT_OID_RAWSZ - size; + to_expell = idx->inbuf_len - to_keep; + + git_hash_update(&idx->trailer, idx->inbuf, to_expell); + + memmove(idx->inbuf, idx->inbuf + to_expell, to_keep); + memcpy(idx->inbuf + to_keep, data, size); + idx->inbuf_len += size - to_expell; +} + +int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats) { int error = -1; - struct git_pack_header hdr; size_t processed; + struct git_pack_header *hdr = &idx->hdr; git_mwindow_file *mwf = &idx->pack->mwf; assert(idx && data && stats); @@ -384,6 +441,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz if (git_filebuf_write(&idx->pack_file, data, size) < 0) return -1; + hash_partially(idx, data, (int)size); + /* Make sure we set the new size of the pack */ if (idx->opened_pack) { idx->pack->mwf.size += size; @@ -399,14 +458,14 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz if (!idx->parsed_header) { unsigned int total_objects; - if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) + if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) return 0; - if (parse_header(&hdr, idx->pack) < 0) + if (parse_header(&idx->hdr, idx->pack) < 0) return -1; idx->parsed_header = 1; - idx->nr_objects = ntohl(hdr.hdr_entries); + idx->nr_objects = ntohl(hdr->hdr_entries); idx->off = sizeof(struct git_pack_header); /* for now, limit to 2^32 objects */ @@ -427,6 +486,9 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz return -1; stats->received_objects = 0; + stats->local_objects = 0; + stats->total_deltas = 0; + stats->indexed_deltas = 0; processed = stats->indexed_objects = 0; stats->total_objects = total_objects; do_progress_callback(idx, stats); @@ -512,6 +574,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz stats->received_objects++; if (do_progress_callback(idx, stats) != 0) { + giterr_clear(); error = GIT_EUSER; goto on_error; } @@ -524,7 +587,7 @@ on_error: return error; } -static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix) +static int index_path(git_buf *path, git_indexer *idx, const char *suffix) { const char prefix[] = "pack-"; size_t slash = (size_t)path->size; @@ -546,68 +609,306 @@ static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char return git_buf_oom(path) ? -1 : 0; } -static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats) +/** + * Rewind the packfile by the trailer, as we might need to fix the + * packfile by injecting objects at the tail and must overwrite it. + */ +static git_off_t seek_back_trailer(git_indexer *idx) +{ + git_off_t off; + + if ((off = p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_CUR)) < 0) + return -1; + + idx->pack->mwf.size -= GIT_OID_RAWSZ; + git_mwindow_free_all(&idx->pack->mwf); + + return off; +} + +static int inject_object(git_indexer *idx, git_oid *id) +{ + git_odb_object *obj; + struct entry *entry; + struct git_pack_entry *pentry; + git_oid foo = {{0}}; + unsigned char hdr[64]; + git_buf buf = GIT_BUF_INIT; + git_off_t entry_start; + const void *data; + size_t len, hdr_len; + int error; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + + entry_start = seek_back_trailer(idx); + + if (git_odb_read(&obj, idx->odb, id) < 0) + return -1; + + data = git_odb_object_data(obj); + len = git_odb_object_size(obj); + + entry->crc = crc32(0L, Z_NULL, 0); + + /* Write out the object header */ + hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj)); + git_filebuf_write(&idx->pack_file, hdr, hdr_len); + idx->pack->mwf.size += hdr_len; + entry->crc = crc32(entry->crc, hdr, hdr_len); + + if ((error = git__compress(&buf, data, len)) < 0) + goto cleanup; + + /* And then the compressed object */ + git_filebuf_write(&idx->pack_file, buf.ptr, buf.size); + idx->pack->mwf.size += buf.size; + entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); + git_buf_free(&buf); + + /* Write a fake trailer so the pack functions play ball */ + if ((error = git_filebuf_write(&idx->pack_file, &foo, GIT_OID_RAWSZ)) < 0) + goto cleanup; + + idx->pack->mwf.size += GIT_OID_RAWSZ; + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GITERR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, id); + git_oid_cpy(&entry->oid, id); + idx->off = entry_start + hdr_len + len; + + if ((error = save_entry(idx, entry, pentry, entry_start)) < 0) + git__free(pentry); + +cleanup: + git_odb_object_free(obj); + return error; +} + +static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) +{ + int error, found_ref_delta = 0; + unsigned int i; + struct delta_info *delta; + size_t size; + git_otype type; + git_mwindow *w = NULL; + git_off_t curpos; + unsigned char *base_info; + unsigned int left = 0; + git_oid base; + + assert(git_vector_length(&idx->deltas) > 0); + + if (idx->odb == NULL) { + giterr_set(GITERR_INDEXER, "cannot fix a thin pack without an ODB"); + return -1; + } + + /* Loop until we find the first REF delta */ + git_vector_foreach(&idx->deltas, i, delta) { + curpos = delta->delta_off; + error = git_packfile_unpack_header(&size, &type, &idx->pack->mwf, &w, &curpos); + git_mwindow_close(&w); + if (error < 0) + return error; + + if (type == GIT_OBJ_REF_DELTA) { + found_ref_delta = 1; + break; + } + } + + if (!found_ref_delta) { + giterr_set(GITERR_INDEXER, "no REF_DELTA found, cannot inject object"); + return -1; + } + + /* curpos now points to the base information, which is an OID */ + base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); + if (base_info == NULL) { + giterr_set(GITERR_INDEXER, "failed to map delta information"); + return -1; + } + + git_oid_fromraw(&base, base_info); + git_mwindow_close(&w); + + if (inject_object(idx, &base) < 0) + return -1; + + stats->local_objects++; + + return 0; +} + +static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats) { unsigned int i; struct delta_info *delta; + int progressed = 0; - git_vector_foreach(&idx->deltas, i, delta) { - git_rawobj obj; + while (idx->deltas.length > 0) { + progressed = 0; + git_vector_foreach(&idx->deltas, i, delta) { + git_rawobj obj; - idx->off = delta->delta_off; - if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) + idx->off = delta->delta_off; + if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) + continue; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + continue; + + git__free(obj.data); + stats->indexed_objects++; + stats->indexed_deltas++; + progressed = 1; + do_progress_callback(idx, stats); + + /* + * Remove this delta from the list and + * decrease i so we don't skip over the next + * delta. + */ + git_vector_remove(&idx->deltas, i); + git__free(delta); + i--; + } + + if (!progressed && (fix_thin_pack(idx, stats) < 0)) { + giterr_set(GITERR_INDEXER, "missing delta bases"); return -1; - - if (hash_and_save(idx, &obj, delta->delta_off) < 0) - return -1; - - git__free(obj.data); - stats->indexed_objects++; - do_progress_callback(idx, stats); + } } return 0; } -int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats) +static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *stats) +{ + void *ptr; + size_t chunk = 1024*1024; + git_off_t hashed = 0; + git_mwindow *w = NULL; + git_mwindow_file *mwf; + unsigned int left; + git_hash_ctx *ctx; + + mwf = &idx->pack->mwf; + ctx = &idx->trailer; + + git_hash_ctx_init(ctx); + git_mwindow_free_all(mwf); + + /* Update the header to include the numer of local objects we injected */ + idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); + if (p_lseek(idx->pack_file.fd, 0, SEEK_SET) < 0) { + giterr_set(GITERR_OS, "failed to seek to the beginning of the pack"); + return -1; + } + + if (p_write(idx->pack_file.fd, &idx->hdr, sizeof(struct git_pack_header)) < 0) { + giterr_set(GITERR_OS, "failed to update the pack header"); + return -1; + } + + /* + * We now use the same technique as before to determine the + * hash. We keep reading up to the end and let + * hash_partially() keep the existing trailer out of the + * calculation. + */ + idx->inbuf_len = 0; + while (hashed < mwf->size) { + ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); + if (ptr == NULL) + return -1; + + hash_partially(idx, ptr, left); + hashed += left; + + git_mwindow_close(&w); + } + + return 0; +} + +int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) { git_mwindow *w = NULL; unsigned int i, long_offsets = 0, left; struct git_pack_idx_header hdr; git_buf filename = GIT_BUF_INIT; struct entry *entry; - void *packfile_hash; - git_oid file_hash; + git_oid trailer_hash, file_hash; git_hash_ctx ctx; git_filebuf index_file = {0}; + void *packfile_trailer; if (git_hash_ctx_init(&ctx) < 0) return -1; /* Test for this before resolve_deltas(), as it plays with idx->off */ - if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) { - giterr_set(GITERR_INDEXER, "Indexing error: unexpected data at the end of the pack"); + if (idx->off < idx->pack->mwf.size - 20) { + giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack"); return -1; } - if (idx->deltas.length > 0) - if (resolve_deltas(idx, stats) < 0) - return -1; + packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); + if (packfile_trailer == NULL) { + git_mwindow_close(&w); + goto on_error; + } + + /* Compare the packfile trailer as it was sent to us and what we calculated */ + git_oid_fromraw(&file_hash, packfile_trailer); + git_mwindow_close(&w); + + git_hash_final(&trailer_hash, &idx->trailer); + if (git_oid_cmp(&file_hash, &trailer_hash)) { + giterr_set(GITERR_INDEXER, "packfile trailer mismatch"); + return -1; + } + + /* Freeze the number of deltas */ + stats->total_deltas = stats->total_objects - stats->indexed_objects; + + if (resolve_deltas(idx, stats) < 0) + return -1; if (stats->indexed_objects != stats->total_objects) { - giterr_set(GITERR_INDEXER, "Indexing error: early EOF"); + giterr_set(GITERR_INDEXER, "early EOF"); return -1; } + if (stats->local_objects > 0) { + if (update_header_and_rehash(idx, stats) < 0) + return -1; + + git_hash_final(&trailer_hash, &idx->trailer); + if (p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_END) < 0) + return -1; + + if (p_write(idx->pack_file.fd, &trailer_hash, GIT_OID_RAWSZ) < 0) { + giterr_set(GITERR_OS, "failed to update pack trailer"); + return -1; + } + } + git_vector_sort(&idx->objects); git_buf_sets(&filename, idx->pack->pack_name); - git_buf_truncate(&filename, filename.size - strlen("pack")); + git_buf_shorten(&filename, strlen("pack")); git_buf_puts(&filename, "idx"); if (git_buf_oom(&filename)) return -1; - if (git_filebuf_open(&index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0) + if (git_filebuf_open(&index_file, filename.ptr, + GIT_FILEBUF_HASH_CONTENTS, idx->mode) < 0) goto on_error; /* Write out the header */ @@ -658,30 +959,22 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress * git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); } - /* Write out the packfile trailer */ - packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); - if (packfile_hash == NULL) { - git_mwindow_close(&w); - goto on_error; - } - - memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ); - git_mwindow_close(&w); - - git_filebuf_write(&index_file, &file_hash, sizeof(git_oid)); - - /* Write out the packfile trailer to the idx file as well */ - if (git_filebuf_hash(&file_hash, &index_file) < 0) + /* Write out the packfile trailer to the index */ + if (git_filebuf_write(&index_file, &trailer_hash, GIT_OID_RAWSZ) < 0) goto on_error; - git_filebuf_write(&index_file, &file_hash, sizeof(git_oid)); + /* Write out the hash of the idx */ + if (git_filebuf_hash(&trailer_hash, &index_file) < 0) + goto on_error; + + git_filebuf_write(&index_file, &trailer_hash, sizeof(git_oid)); /* Figure out what the final name should be */ - if (index_path_stream(&filename, idx, ".idx") < 0) + if (index_path(&filename, idx, ".idx") < 0) goto on_error; /* Commit file */ - if (git_filebuf_commit_at(&index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) + if (git_filebuf_commit_at(&index_file, filename.ptr) < 0) goto on_error; git_mwindow_free_all(&idx->pack->mwf); @@ -689,10 +982,10 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress * p_close(idx->pack->mwf.fd); idx->pack->mwf.fd = -1; - if (index_path_stream(&filename, idx, ".pack") < 0) + if (index_path(&filename, idx, ".pack") < 0) goto on_error; /* And don't forget to rename the packfile to its new place. */ - if (git_filebuf_commit_at(&idx->pack_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) + if (git_filebuf_commit_at(&idx->pack_file, filename.ptr) < 0) return -1; git_buf_free(&filename); @@ -706,7 +999,7 @@ on_error: return -1; } -void git_indexer_stream_free(git_indexer_stream *idx) +void git_indexer_free(git_indexer *idx) { khiter_t k; unsigned int i; diff --git a/src/iterator.c b/src/iterator.c index 5917f63fd..8646399ab 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -893,6 +893,7 @@ struct fs_iterator { git_index_entry entry; git_buf path; size_t root_len; + uint32_t dirload_flags; int depth; int (*enter_dir_cb)(fs_iterator *self); @@ -986,12 +987,25 @@ static int fs_iterator__expand_dir(fs_iterator *fi) GITERR_CHECK_ALLOC(ff); error = git_path_dirload_with_stat( - fi->path.ptr, fi->root_len, iterator__ignore_case(fi), + fi->path.ptr, fi->root_len, fi->dirload_flags, fi->base.start, fi->base.end, &ff->entries); if (error < 0) { + git_error last_error = {0}; + + giterr_detach(&last_error); + + /* these callbacks may clear the error message */ fs_iterator__free_frame(ff); fs_iterator__advance_over(NULL, (git_iterator *)fi); + /* next time return value we skipped to */ + fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + if (last_error.message) { + giterr_set_str(last_error.klass, last_error.message); + free(last_error.message); + } + return error; } @@ -1174,7 +1188,7 @@ static int fs_iterator__update_entry(fs_iterator *fi) return GIT_ITEROVER; fi->entry.path = ps->path; - git_index_entry__init_from_stat(&fi->entry, &ps->st); + 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); @@ -1207,6 +1221,11 @@ static int fs_iterator__initialize( } fi->root_len = fi->path.size; + fi->dirload_flags = + (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) | + (iterator__flag(fi, PRECOMPOSE_UNICODE) ? + GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0); + if ((error = fs_iterator__expand_dir(fi)) < 0) { if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) { giterr_clear(); @@ -1329,7 +1348,7 @@ int git_iterator_for_workdir_ext( const char *start, const char *end) { - int error; + int error, precompose = 0; workdir_iterator *wi; if (!repo_workdir) { @@ -1350,12 +1369,18 @@ int git_iterator_for_workdir_ext( wi->fi.update_entry_cb = workdir_iterator__update_entry; if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 || - (error = git_ignore__for_path(repo, "", &wi->ignores)) < 0) + (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0) { git_iterator_free((git_iterator *)wi); return error; } + /* try to look up precompose and set flag if appropriate */ + if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0) + giterr_clear(); + else if (precompose) + wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + return fs_iterator__initialize(out, &wi->fi, repo_workdir); } diff --git a/src/iterator.h b/src/iterator.h index ea88fa6a2..751e139d0 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -24,13 +24,15 @@ typedef enum { typedef enum { /** ignore case for entry sort order */ - GIT_ITERATOR_IGNORE_CASE = (1 << 0), + GIT_ITERATOR_IGNORE_CASE = (1u << 0), /** force case sensitivity for entry sort order */ - GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), + GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), /** return tree items in addition to blob items */ - GIT_ITERATOR_INCLUDE_TREES = (1 << 2), + GIT_ITERATOR_INCLUDE_TREES = (1u << 2), /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ - GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3), + GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), + /** convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), } git_iterator_flag_t; typedef struct { diff --git a/src/merge.c b/src/merge.c index 82d2e6f37..115867971 100644 --- a/src/merge.c +++ b/src/merge.c @@ -58,7 +58,7 @@ struct merge_diff_df_data { /* Merge base computation */ -int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length) +int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) { git_revwalk *walk; git_vector list; @@ -285,7 +285,7 @@ int git_repository_mergehead_foreach(git_repository *repo, if ((error = git_oid_fromstr(&oid, line)) < 0) goto cleanup; - if (cb(&oid, payload) < 0) { + if (cb(&oid, payload) != 0) { error = GIT_EUSER; goto cleanup; } @@ -1615,17 +1615,14 @@ static int write_orig_head( { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; - char orig_oid_str[GIT_OID_HEXSZ + 1]; int error = 0; assert(repo && our_head); - git_oid_tostr(orig_oid_str, GIT_OID_HEXSZ+1, &our_head->oid); - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) == 0 && - (error = git_filebuf_printf(&file, "%s\n", orig_oid_str)) == 0) - error = git_filebuf_commit(&file, 0666); + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 && + (error = git_filebuf_printf(&file, "%s\n", our_head->oid_str)) == 0) + error = git_filebuf_commit(&file); if (error < 0) git_filebuf_cleanup(&file); @@ -1642,24 +1639,21 @@ static int write_merge_head( { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; - char merge_oid_str[GIT_OID_HEXSZ + 1]; size_t i; int error = 0; assert(repo && heads); if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0) + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; for (i = 0; i < heads_len; i++) { - git_oid_tostr(merge_oid_str, GIT_OID_HEXSZ+1, &heads[i]->oid); - - if ((error = git_filebuf_printf(&file, "%s\n", merge_oid_str)) < 0) + if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->oid_str)) < 0) goto cleanup; } - error = git_filebuf_commit(&file, 0666); + error = git_filebuf_commit(&file); cleanup: if (error < 0) @@ -1682,10 +1676,21 @@ static int write_merge_mode(git_repository *repo, unsigned int flags) assert(repo); if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0) + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; - error = git_filebuf_commit(&file, 0666); + /* + * no-ff is the only thing allowed here at present. One would + * presume they would be space-delimited when there are more, but + * this needs to be revisited. + */ + + if (flags & GIT_MERGE_NO_FASTFORWARD) { + if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&file); cleanup: if (error < 0) @@ -1890,7 +1895,6 @@ static int write_merge_msg( { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; - char oid_str[GIT_OID_HEXSZ + 1]; struct merge_msg_entry *entries; git_vector matching = GIT_VECTOR_INIT; size_t i; @@ -1902,14 +1906,16 @@ static int write_merge_msg( entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); GITERR_CHECK_ALLOC(entries); - if (git_vector_init(&matching, heads_len, NULL) < 0) + if (git_vector_init(&matching, heads_len, NULL) < 0) { + git__free(entries); return -1; + } for (i = 0; i < heads_len; i++) entries[i].merge_head = heads[i]; if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 || (error = git_filebuf_write(&file, "Merge ", 6)) < 0) goto cleanup; @@ -1928,10 +1934,9 @@ static int write_merge_msg( if (!msg_entry_is_oid(&entries[i])) break; - git_oid_fmt(oid_str, &entries[i].merge_head->oid); - oid_str[GIT_OID_HEXSZ] = '\0'; - - if ((error = git_filebuf_printf(&file, "%scommit '%s'", (i > 0) ? "; " : "", oid_str)) < 0) + if ((error = git_filebuf_printf(&file, + "%scommit '%s'", (i > 0) ? "; " : "", + entries[i].merge_head->oid_str)) < 0) goto cleanup; entries[i].written = 1; @@ -1978,15 +1983,13 @@ static int write_merge_msg( if (merge_msg_entry_written(&entries[i])) continue; - git_oid_fmt(oid_str, &entries[i].merge_head->oid); - oid_str[GIT_OID_HEXSZ] = '\0'; - - if ((error = git_filebuf_printf(&file, "; commit '%s'", oid_str)) < 0) + if ((error = git_filebuf_printf(&file, "; commit '%s'", + entries[i].merge_head->oid_str)) < 0) goto cleanup; } if ((error = git_filebuf_printf(&file, "\n")) < 0 || - (error = git_filebuf_commit(&file, 0666)) < 0) + (error = git_filebuf_commit(&file)) < 0) goto cleanup; cleanup: @@ -2001,6 +2004,477 @@ cleanup: return error; } +/* Merge branches */ + +static int merge_ancestor_head( + git_merge_head **ancestor_head, + git_repository *repo, + const git_merge_head *our_head, + const git_merge_head **their_heads, + size_t their_heads_len) +{ + git_oid *oids, ancestor_oid; + size_t i; + int error = 0; + + assert(repo && our_head && their_heads); + + oids = git__calloc(their_heads_len + 1, sizeof(git_oid)); + GITERR_CHECK_ALLOC(oids); + + git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); + + for (i = 0; i < their_heads_len; i++) + git_oid_cpy(&oids[i + 1], &their_heads[i]->oid); + + if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) + goto on_error; + + error = git_merge_head_from_oid(ancestor_head, repo, &ancestor_oid); + +on_error: + git__free(oids); + return error; +} + +GIT_INLINE(bool) merge_check_uptodate( + git_merge_result *result, + const git_merge_head *ancestor_head, + const git_merge_head *their_head) +{ + if (git_oid_cmp(&ancestor_head->oid, &their_head->oid) == 0) { + result->is_uptodate = 1; + return true; + } + + return false; +} + +GIT_INLINE(bool) merge_check_fastforward( + git_merge_result *result, + const git_merge_head *ancestor_head, + const git_merge_head *our_head, + const git_merge_head *their_head, + unsigned int flags) +{ + if ((flags & GIT_MERGE_NO_FASTFORWARD) == 0 && + git_oid_cmp(&ancestor_head->oid, &our_head->oid) == 0) { + result->is_fastforward = 1; + git_oid_cpy(&result->fastforward_oid, &their_head->oid); + + return true; + } + + return false; +} + +const char *merge_their_label(const char *branchname) +{ + const char *slash; + + if ((slash = strrchr(branchname, '/')) == NULL) + return branchname; + + if (*(slash+1) == '\0') + return "theirs"; + + return slash+1; +} + +static int merge_normalize_opts( + git_repository *repo, + git_merge_opts *opts, + const git_merge_opts *given, + size_t their_heads_len, + const git_merge_head **their_heads) +{ + int error = 0; + unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE_CREATE | + GIT_CHECKOUT_ALLOW_CONFLICTS; + + GIT_UNUSED(repo); + + if (given != NULL) + memcpy(opts, given, sizeof(git_merge_opts)); + else { + git_merge_opts default_opts = GIT_MERGE_OPTS_INIT; + memcpy(opts, &default_opts, sizeof(git_merge_opts)); + } + + if (!opts->checkout_opts.checkout_strategy) + opts->checkout_opts.checkout_strategy = default_checkout_strategy; + + if (!opts->checkout_opts.our_label) + opts->checkout_opts.our_label = "HEAD"; + + if (!opts->checkout_opts.their_label) { + if (their_heads_len == 1 && their_heads[0]->ref_name) + opts->checkout_opts.their_label = merge_their_label(their_heads[0]->ref_name); + else if (their_heads_len == 1) + opts->checkout_opts.their_label = their_heads[0]->oid_str; + else + opts->checkout_opts.their_label = "theirs"; + } + + return error; +} + +static int merge_affected_paths(git_vector *paths, git_repository *repo, git_index *index_new) +{ + git_tree *head_tree = NULL; + git_iterator *iter_head = NULL, *iter_new = NULL; + git_diff *merged_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_delta *delta; + size_t i; + const git_index_entry *e; + char *path; + int error = 0; + + 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_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) + goto done; + + git_vector_foreach(&merged_list->deltas, i, delta) { + path = git__strdup(delta->new_file.path); + GITERR_CHECK_ALLOC(path); + + if ((error = git_vector_insert(paths, path)) < 0) + goto on_error; + } + + for (i = 0; i < git_index_entrycount(index_new); i++) { + e = git_index_get_byindex(index_new, i); + + if (git_index_entry_stage(e) != 0 && + (git_vector_last(paths) == NULL || + strcmp(git_vector_last(paths), e->path) != 0)) { + + path = git__strdup(e->path); + GITERR_CHECK_ALLOC(path); + + if ((error = git_vector_insert(paths, path)) < 0) + goto on_error; + } + } + + goto done; + +on_error: + git_vector_foreach(paths, i, path) + git__free(path); + + git_vector_clear(paths); + +done: + git_tree_free(head_tree); + git_iterator_free(iter_head); + git_iterator_free(iter_new); + git_diff_free(merged_list); + + return error; +} + +static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_tree *head_tree = NULL; + git_index *index_repo = NULL; + git_iterator *iter_repo = NULL, *iter_new = NULL; + git_diff *staged_diff_list = NULL, *index_diff_list = NULL; + git_diff_delta *delta; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector staged_paths = GIT_VECTOR_INIT; + size_t i; + int error = 0; + + GIT_UNUSED(merged_paths); + + *conflicts = 0; + + /* No staged changes may exist unless the change staged is identical to + * the result of the merge. This allows one to apply to merge manually, + * then run merge. Any other staged change would be overwritten by + * a reset merge. + */ + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_repository_index(&index_repo, repo)) < 0 || + (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) + goto done; + + if (staged_diff_list->deltas.length == 0) + goto done; + + git_vector_foreach(&staged_diff_list->deltas, i, delta) { + if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) + goto done; + } + + opts.pathspec.count = staged_paths.length; + opts.pathspec.strings = (char **)staged_paths.contents; + + 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 || + (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) + goto done; + + *conflicts = index_diff_list->deltas.length; + +done: + git_tree_free(head_tree); + git_index_free(index_repo); + git_iterator_free(iter_repo); + git_iterator_free(iter_new); + git_diff_free(staged_diff_list); + git_diff_free(index_diff_list); + git_vector_free(&staged_paths); + + return error; +} + +static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_tree *head_tree = NULL; + git_diff *wd_diff_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + int error = 0; + + GIT_UNUSED(index_new); + + *conflicts = 0; + + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0) + goto done; + + /* Workdir changes may exist iff they do not conflict with changes that + * will be applied by the merge (including conflicts). Ensure that there + * are no changes in the workdir to these paths. + */ + opts.pathspec.count = merged_paths->length; + opts.pathspec.strings = (char **)merged_paths->contents; + + if ((error = git_diff_tree_to_workdir(&wd_diff_list, repo, head_tree, &opts)) < 0) + goto done; + + *conflicts = wd_diff_list->deltas.length; + +done: + git_tree_free(head_tree); + git_diff_free(wd_diff_list); + + return error; +} + +static int merge_indexes(git_repository *repo, git_index *index_new) +{ + git_index *index_repo; + unsigned int index_repo_caps; + git_vector paths = GIT_VECTOR_INIT; + size_t index_conflicts = 0, wd_conflicts = 0, conflicts, i; + char *path; + const git_index_entry *e; + const git_index_name_entry *name; + const git_index_reuc_entry *reuc; + int error = 0; + + if ((error = git_repository_index(&index_repo, repo)) < 0) + goto done; + + /* Set the index to case sensitive to handle the merge */ + index_repo_caps = git_index_caps(index_repo); + + if ((error = git_index_set_caps(index_repo, (index_repo_caps & ~GIT_INDEXCAP_IGNORE_CASE))) < 0) + goto done; + + /* Make sure the index and workdir state do not prevent merging */ + if ((error = merge_affected_paths(&paths, repo, index_new)) < 0 || + (error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || + (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) + goto done; + + if ((conflicts = index_conflicts + wd_conflicts) > 0) { + giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge", + conflicts, (conflicts != 1) ? "s" : ""); + error = GIT_EMERGECONFLICT; + + goto done; + } + + /* Update the new index */ + git_vector_foreach(&paths, i, path) { + if ((e = git_index_get_bypath(index_new, path, 0)) != NULL) + error = git_index_add(index_repo, e); + else + error = git_index_remove(index_repo, path, 0); + } + + /* Add conflicts */ + git_index_conflict_cleanup(index_repo); + + for (i = 0; i < git_index_entrycount(index_new); i++) { + e = git_index_get_byindex(index_new, i); + + if (git_index_entry_stage(e) != 0 && + (error = git_index_add(index_repo, e)) < 0) + goto done; + } + + /* Add name entries */ + git_index_name_clear(index_repo); + + for (i = 0; i < git_index_name_entrycount(index_new); i++) { + name = git_index_name_get_byindex(index_new, i); + + if ((error = git_index_name_add(index_repo, + name->ancestor, name->ours, name->theirs)) < 0) + goto done; + } + + /* Add the reuc */ + git_index_reuc_clear(index_repo); + + for (i = 0; i < git_index_reuc_entrycount(index_new); i++) { + reuc = (git_index_reuc_entry *)git_index_reuc_get_byindex(index_new, i); + + if ((error = git_index_reuc_add(index_repo, reuc->path, + reuc->mode[0], &reuc->oid[0], + reuc->mode[1], &reuc->oid[1], + reuc->mode[2], &reuc->oid[2])) < 0) + goto done; + } + +done: + if (index_repo != NULL) + git_index_set_caps(index_repo, index_repo_caps); + + git_index_free(index_repo); + + git_vector_foreach(&paths, i, path) + git__free(path); + + git_vector_free(&paths); + + return error; +} + +int git_merge( + git_merge_result **out, + git_repository *repo, + const git_merge_head **their_heads, + size_t their_heads_len, + const git_merge_opts *given_opts) +{ + git_merge_result *result; + git_merge_opts opts; + git_reference *our_ref = NULL; + git_merge_head *ancestor_head = NULL, *our_head = NULL; + git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL; + git_index *index_new = NULL, *index_repo = NULL; + size_t i; + int error = 0; + + assert(out && repo && their_heads); + + *out = NULL; + + if (their_heads_len != 1) { + giterr_set(GITERR_MERGE, "Can only merge a single branch"); + return -1; + } + + result = git__calloc(1, sizeof(git_merge_result)); + GITERR_CHECK_ALLOC(result); + + their_trees = git__calloc(their_heads_len, sizeof(git_tree *)); + GITERR_CHECK_ALLOC(their_trees); + + if ((error = merge_normalize_opts(repo, &opts, given_opts, their_heads_len, their_heads)) < 0) + goto on_error; + + if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) + goto on_error; + + if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 || + (error = git_merge_head_from_ref(&our_head, repo, our_ref)) < 0) + goto on_error; + + if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (their_heads_len == 1 && + ancestor_head != NULL && + (merge_check_uptodate(result, ancestor_head, their_heads[0]) || + merge_check_fastforward(result, ancestor_head, our_head, their_heads[0], opts.merge_flags))) { + *out = result; + goto done; + } + + /* If FASTFORWARD_ONLY is specified, fail. */ + if ((opts.merge_flags & GIT_MERGE_FASTFORWARD_ONLY) == + GIT_MERGE_FASTFORWARD_ONLY) { + giterr_set(GITERR_MERGE, "Not a fast-forward."); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + /* Write the merge files to the repository. */ + if ((error = git_merge__setup(repo, our_head, their_heads, their_heads_len, opts.merge_flags)) < 0) + goto on_error; + + if (ancestor_head != NULL && + (error = git_commit_tree(&ancestor_tree, ancestor_head->commit)) < 0) + goto on_error; + + if ((error = git_commit_tree(&our_tree, our_head->commit)) < 0) + goto on_error; + + 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_new, repo, ancestor_tree, our_tree, their_trees[0], &opts.merge_tree_opts)) < 0 || + (error = merge_indexes(repo, index_new)) < 0 || + (error = git_repository_index(&index_repo, repo)) < 0 || + (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0) + goto on_error; + + result->index = index_new; + + *out = result; + goto done; + +on_error: + git_repository_merge_cleanup(repo); + + git_index_free(index_new); + git__free(result); + +done: + git_index_free(index_repo); + + 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_merge_head_free(our_head); + git_merge_head_free(ancestor_head); + + git_reference_free(our_ref); + + return error; +} + int git_merge__setup( git_repository *repo, const git_merge_head *our_head, @@ -2011,7 +2485,7 @@ int git_merge__setup( int error = 0; assert (repo && our_head && heads); - + if ((error = write_orig_head(repo, our_head)) == 0 && (error = write_merge_head(repo, heads, heads_len)) == 0 && (error = write_merge_mode(repo, flags)) == 0) { @@ -2054,6 +2528,41 @@ cleanup: return error; } +/* Merge result data */ + +int git_merge_result_is_uptodate(git_merge_result *merge_result) +{ + assert(merge_result); + + return merge_result->is_uptodate; +} + +int git_merge_result_is_fastforward(git_merge_result *merge_result) +{ + assert(merge_result); + + return merge_result->is_fastforward; +} + +int git_merge_result_fastforward_oid(git_oid *out, git_merge_result *merge_result) +{ + assert(out && merge_result); + + git_oid_cpy(out, &merge_result->fastforward_oid); + return 0; +} + +void git_merge_result_free(git_merge_result *merge_result) +{ + if (merge_result == NULL) + return; + + git_index_free(merge_result->index); + merge_result->index = NULL; + + git__free(merge_result); +} + /* Merge heads are the input to merge */ static int merge_head_init( @@ -2085,6 +2594,9 @@ static int merge_head_init( git_oid_cpy(&head->oid, oid); + git_oid_fmt(head->oid_str, oid); + head->oid_str[GIT_OID_HEXSZ] = '\0'; + if ((error = git_commit_lookup(&head->commit, repo, &head->oid)) < 0) { git_merge_head_free(head); return error; diff --git a/src/merge.h b/src/merge.h index ba6725de9..d7d1c67b7 100644 --- a/src/merge.h +++ b/src/merge.h @@ -16,6 +16,7 @@ #define GIT_MERGE_MSG_FILE "MERGE_MSG" #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 @@ -113,9 +114,20 @@ struct git_merge_head { char *remote_url; git_oid oid; + char oid_str[GIT_OID_HEXSZ+1]; git_commit *commit; }; +/** Internal structure for merge results */ +struct git_merge_result { + bool is_uptodate; + + bool is_fastforward; + git_oid fastforward_oid; + + git_index *index; +}; + int git_merge__bases_many( git_commit_list **out, git_revwalk *walk, diff --git a/src/merge_file.c b/src/merge_file.c index c3477ccb9..48fc46e57 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -47,7 +47,7 @@ GIT_INLINE(int) merge_file_best_mode( * assume executable. Otherwise, if any mode changed from the ancestor, * use that one. */ - if (GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) { + if (!GIT_MERGE_FILE_SIDE_EXISTS(ancestor)) { if (ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE || theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE) return GIT_FILEMODE_BLOB_EXECUTABLE; diff --git a/src/netops.c b/src/netops.c index 69179dd1c..ad27d84cf 100644 --- a/src/netops.c +++ b/src/netops.c @@ -19,10 +19,6 @@ # endif #endif -#ifdef __FreeBSD__ -# include -#endif - #ifdef GIT_SSL # include # include @@ -36,6 +32,7 @@ #include "netops.h" #include "posix.h" #include "buffer.h" +#include "http_parser.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -577,55 +574,161 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); } +static const char *prefix_http = "http://"; +static const char *prefix_https = "https://"; + +int gitno_connection_data_from_url( + gitno_connection_data *data, + const char *url, + const char *service_suffix) +{ + int error = -1; + const char *default_port = NULL, *path_search_start = NULL; + char *original_host = NULL; + + /* service_suffix is optional */ + assert(data && url); + + /* Save these for comparison later */ + original_host = data->host; + data->host = NULL; + gitno_connection_data_free_ptrs(data); + + if (!git__prefixcmp(url, prefix_http)) { + path_search_start = url + strlen(prefix_http); + default_port = "80"; + + if (data->use_ssl) { + giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed"); + goto cleanup; + } + } else if (!git__prefixcmp(url, prefix_https)) { + path_search_start = url + strlen(prefix_https); + default_port = "443"; + data->use_ssl = true; + } else if (url[0] == '/') + default_port = data->use_ssl ? "443" : "80"; + + if (!default_port) { + giterr_set(GITERR_NET, "Unrecognized URL prefix"); + goto cleanup; + } + + error = gitno_extract_url_parts( + &data->host, &data->port, &data->path, &data->user, &data->pass, + url, default_port); + + if (url[0] == '/') { + /* Relative redirect; reuse original host name and port */ + path_search_start = url; + git__free(data->host); + data->host = original_host; + original_host = NULL; + } + + if (!error) { + const char *path = strchr(path_search_start, '/'); + size_t pathlen = strlen(path); + size_t suffixlen = service_suffix ? strlen(service_suffix) : 0; + + if (suffixlen && + !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) { + git__free(data->path); + data->path = git__strndup(path, pathlen - suffixlen); + } else { + git__free(data->path); + data->path = git__strdup(path); + } + + /* Check for errors in the resulting data */ + if (original_host && url[0] != '/' && strcmp(original_host, data->host)) { + giterr_set(GITERR_NET, "Cross host redirect not allowed"); + error = -1; + } + } + +cleanup: + if (original_host) git__free(original_host); + return error; +} + +void gitno_connection_data_free_ptrs(gitno_connection_data *d) +{ + git__free(d->host); d->host = NULL; + git__free(d->port); d->port = NULL; + git__free(d->path); d->path = NULL; + git__free(d->user); d->user = NULL; + git__free(d->pass); d->pass = NULL; +} + +#define hex2c(c) ((c | 32) % 39 - 9) +static char* unescape(char *str) +{ + int x, y; + int len = (int)strlen(str); + + for (x=y=0; str[y]; ++x, ++y) { + if ((str[x] = str[y]) == '%') { + if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) { + str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]); + y += 2; + } + } + } + str[x] = '\0'; + return str; +} + int gitno_extract_url_parts( char **host, char **port, + char **path, char **username, char **password, const char *url, const char *default_port) { - char *colon, *slash, *at, *end; - const char *start; + struct http_parser_url u = {0}; + const char *_host, *_port, *_path, *_userinfo; - /* - * - * ==> [user[:pass]@]hostname.tld[:port]/resource - */ - - colon = strchr(url, ':'); - slash = strchr(url, '/'); - at = strchr(url, '@'); - - if (slash == NULL) { - giterr_set(GITERR_NET, "Malformed URL: missing /"); - return -1; + if (http_parser_parse_url(url, strlen(url), false, &u)) { + giterr_set(GITERR_NET, "Malformed URL '%s'", url); + return GIT_EINVALIDSPEC; } - start = url; - if (at && at < slash) { - start = at+1; - *username = git__substrdup(url, at - url); + _host = url+u.field_data[UF_HOST].off; + _port = url+u.field_data[UF_PORT].off; + _path = url+u.field_data[UF_PATH].off; + _userinfo = url+u.field_data[UF_USERINFO].off; + + if (u.field_set & (1 << UF_HOST)) { + *host = git__substrdup(_host, u.field_data[UF_HOST].len); + GITERR_CHECK_ALLOC(*host); } - if (colon && colon < at) { - git__free(*username); - *username = git__substrdup(url, colon-url); - *password = git__substrdup(colon+1, at-colon-1); - colon = strchr(at, ':'); - } - - if (colon == NULL) { + if (u.field_set & (1 << UF_PORT)) + *port = git__substrdup(_port, u.field_data[UF_PORT].len); + else *port = git__strdup(default_port); - } else { - *port = git__substrdup(colon + 1, slash - colon - 1); - } GITERR_CHECK_ALLOC(*port); - end = colon == NULL ? slash : colon; + if (u.field_set & (1 << UF_PATH)) { + *path = git__substrdup(_path, u.field_data[UF_PATH].len); + GITERR_CHECK_ALLOC(*path); + } - *host = git__substrdup(start, end - start); - GITERR_CHECK_ALLOC(*host); + if (u.field_set & (1 << UF_USERINFO)) { + const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len); + if (colon) { + *username = unescape(git__substrdup(_userinfo, colon - _userinfo)); + *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo))); + GITERR_CHECK_ALLOC(*password); + } else { + *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len); + } + GITERR_CHECK_ALLOC(*username); + + } return 0; } diff --git a/src/netops.h b/src/netops.h index d352bf3b6..666d66b12 100644 --- a/src/netops.h +++ b/src/netops.h @@ -66,9 +66,33 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags); int gitno_close(gitno_socket *s); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); +typedef struct gitno_connection_data { + char *host; + char *port; + char *path; + char *user; + char *pass; + bool use_ssl; +} gitno_connection_data; + +/* + * This replaces all the pointers in `data` with freshly-allocated strings, + * that the caller is responsible for freeing. + * `gitno_connection_data_free_ptrs` is good for this. + */ + +int gitno_connection_data_from_url( + gitno_connection_data *data, + const char *url, + const char *service_suffix); + +/* This frees all the pointers IN the struct, but not the struct itself. */ +void gitno_connection_data_free_ptrs(gitno_connection_data *data); + int gitno_extract_url_parts( char **host, char **port, + char **path, char **username, char **password, const char *url, diff --git a/src/object.c b/src/object.c index 9b8ccdd3e..3fc984b45 100644 --- a/src/object.c +++ b/src/object.c @@ -364,3 +364,38 @@ int git_object_dup(git_object **dest, git_object *source) *dest = source; return 0; } + +int git_object_lookup_bypath( + git_object **out, + const git_object *treeish, + const char *path, + git_otype type) +{ + int error = -1; + git_tree *tree = NULL; + git_tree_entry *entry = NULL; + + assert(out && treeish && path); + + if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE) < 0) || + (error = git_tree_entry_bypath(&entry, tree, path)) < 0) + { + goto cleanup; + } + + if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type) + { + giterr_set(GITERR_OBJECT, + "object at path '%s' is not of the asked-for type %d", + path, type); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + error = git_tree_entry_to_object(out, git_object_owner(treeish), entry); + +cleanup: + git_tree_entry_free(entry); + git_tree_free(tree); + return error; +} diff --git a/src/odb.c b/src/odb.c index 8e62efd00..b208b279e 100644 --- a/src/odb.c +++ b/src/odb.c @@ -124,6 +124,13 @@ git_otype git_odb_object_type(git_odb_object *object) return object->cached.type; } +int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + void git_odb_object_free(git_odb_object *object) { if (object == NULL) @@ -168,7 +175,6 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) error = -1; goto done; - return -1; } error = git_hash_final(out, &ctx); @@ -179,28 +185,30 @@ done: } int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters) + git_oid *out, git_file fd, size_t size, git_otype type, git_filter_list *fl) { int error; git_buf raw = GIT_BUF_INIT; - git_buf filtered = GIT_BUF_INIT; - if (!filters || !filters->length) + if (!fl) return git_odb__hashfd(out, fd, size, type); /* size of data is used in header, so we have to read the whole file * into memory to apply filters before beginning to calculate the hash */ - if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) - error = git_filters_apply(&filtered, &raw, filters); + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { + git_buf post = GIT_BUF_INIT; - git_buf_free(&raw); + error = git_filter_list_apply_to_data(&post, fl, &raw); - if (!error) - error = git_odb_hash(out, filtered.ptr, filtered.size, type); + git_buf_free(&raw); - git_buf_free(&filtered); + if (!error) + error = git_odb_hash(out, post.ptr, post.size, type); + + git_buf_free(&post); + } return error; } @@ -232,6 +240,7 @@ int git_odb__hashlink(git_oid *out, const char *path) link_data[size] = '\0'; if (read_len != (ssize_t)size) { giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); + git__free(link_data); return -1; } @@ -290,10 +299,10 @@ typedef struct { git_otype type; } fake_wstream; -static int fake_wstream__fwrite(git_oid *oid, git_odb_stream *_stream) +static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) { fake_wstream *stream = (fake_wstream *)_stream; - return _stream->backend->write(oid, _stream->backend, stream->buffer, stream->size, stream->type); + return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); } static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) @@ -444,7 +453,7 @@ int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) return 0; } - giterr_set(GITERR_ODB, "No ODB backend loaded at index " PRIuZ, pos); + giterr_set(GITERR_ODB, "No ODB backend loaded at index %" PRIuZ, pos); return GIT_ENOTFOUND; } @@ -483,7 +492,7 @@ static int add_default_backends( #endif /* add the loose object backend */ - if (git_odb_backend_loose(&loose, objects_dir, -1, 0) < 0 || + if (git_odb_backend_loose(&loose, objects_dir, -1, 0, 0, 0) < 0 || add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates, inode) < 0) return -1; @@ -607,7 +616,6 @@ int git_odb_exists(git_odb *db, const git_oid *id) git_odb_object *object; size_t i; bool found = false; - bool refreshed = false; assert(db && id); @@ -616,23 +624,12 @@ int git_odb_exists(git_odb *db, const git_oid *id) return (int)true; } -attempt_lookup: 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 (b->exists != NULL) - found = b->exists(b, id); - } - - if (!found && !refreshed) { - if (git_odb_refresh(db) < 0) { - giterr_clear(); - return (int)false; - } - - refreshed = true; - goto attempt_lookup; + found = (bool)b->exists(b, id); } return (int)found; @@ -699,7 +696,6 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) { size_t i, reads = 0; int error; - bool refreshed = false; git_rawobj raw; git_odb_object *object; @@ -709,7 +705,6 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) if (*out != NULL) return 0; -attempt_lookup: error = GIT_ENOTFOUND; for (i = 0; i < db->backends.length && error < 0; ++i) { @@ -722,14 +717,6 @@ attempt_lookup: } } - if (error == GIT_ENOTFOUND && !refreshed) { - if ((error = git_odb_refresh(db)) < 0) - return error; - - refreshed = true; - goto attempt_lookup; - } - if (error && error != GIT_PASSTHROUGH) { if (!reads) return git_odb__error_notfound("no match for id", id); @@ -751,7 +738,7 @@ int git_odb_read_prefix( git_oid found_full_oid = {{0}}; git_rawobj raw; void *data = NULL; - bool found = false, refreshed = false; + bool found = false; git_odb_object *object; assert(out && db); @@ -768,7 +755,6 @@ int git_odb_read_prefix( return 0; } -attempt_lookup: for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; @@ -785,22 +771,16 @@ attempt_lookup: git__free(data); data = raw.data; - if (found && git_oid__cmp(&full_oid, &found_full_oid)) + if (found && git_oid__cmp(&full_oid, &found_full_oid)) { + git__free(raw.data); return git_odb__error_ambiguous("multiple matches for prefix"); + } found_full_oid = full_oid; found = true; } } - if (!found && !refreshed) { - if ((error = git_odb_refresh(db)) < 0) - return error; - - refreshed = true; - goto attempt_lookup; - } - if (!found) return git_odb__error_notfound("no match for prefix", short_id); @@ -848,7 +828,7 @@ int git_odb_write( continue; if (b->write != NULL) - error = b->write(oid, b, data, len, type); + error = b->write(b, oid, data, len, type); } if (!error || error == GIT_PASSTHROUGH) @@ -862,17 +842,27 @@ int git_odb_write( return error; stream->write(stream, data, len); - error = stream->finalize_write(oid, stream); - stream->free(stream); + error = stream->finalize_write(stream, oid); + git_odb_stream_free(stream); return error; } +static void hash_header(git_hash_ctx *ctx, size_t size, git_otype type) +{ + char header[64]; + int hdrlen; + + hdrlen = git_odb__format_object_header(header, sizeof(header), size, type); + git_hash_update(ctx, header, hdrlen); +} + int git_odb_open_wstream( git_odb_stream **stream, git_odb *db, size_t size, git_otype type) { size_t i, writes = 0; int error = GIT_ERROR; + git_hash_ctx *ctx; assert(stream && db); @@ -898,9 +888,71 @@ int git_odb_open_wstream( if (error < 0 && !writes) error = git_odb__error_unsupported_in_backend("write object"); + ctx = git__malloc(sizeof(git_hash_ctx)); + GITERR_CHECK_ALLOC(ctx); + + + git_hash_ctx_init(ctx); + hash_header(ctx, size, type); + (*stream)->hash_ctx = ctx; + + (*stream)->declared_size = size; + (*stream)->received_bytes = 0; + return error; } +static int git_odb_stream__invalid_length( + const git_odb_stream *stream, + const char *action) +{ + giterr_set(GITERR_ODB, + "Cannot %s - " + "Invalid length. %"PRIuZ" was expected. The " + "total size of the received chunks amounts to %"PRIuZ".", + action, stream->declared_size, stream->received_bytes); + + return -1; +} + +int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) +{ + git_hash_update(stream->hash_ctx, buffer, len); + + stream->received_bytes += len; + + if (stream->received_bytes > stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_write()"); + + return stream->write(stream, buffer, len); +} + +int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) +{ + if (stream->received_bytes != stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_finalize_write()"); + + git_hash_final(out, stream->hash_ctx); + + if (git_odb_exists(stream->backend->odb, out)) + return 0; + + return stream->finalize_write(stream, out); +} + +int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) +{ + return stream->read(stream, buffer, len); +} + +void git_odb_stream_free(git_odb_stream *stream) +{ + git__free(stream->hash_ctx); + stream->free(stream); +} + int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) { size_t i, reads = 0; @@ -943,7 +995,7 @@ int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer if (b->writepack != NULL) { ++writes; - error = b->writepack(out, b, progress_cb, progress_payload); + error = b->writepack(out, b, db, progress_cb, progress_payload); } } diff --git a/src/odb.h b/src/odb.h index 0d9f9e2ea..61dd9a7fd 100644 --- a/src/odb.h +++ b/src/odb.h @@ -14,6 +14,7 @@ #include "vector.h" #include "cache.h" #include "posix.h" +#include "filter.h" #define GIT_OBJECTS_DIR "objects/" #define GIT_OBJECT_DIR_MODE 0777 @@ -66,7 +67,7 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); * Acts just like git_odb__hashfd with the addition of filters... */ int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters); + git_oid *out, git_file fd, size_t len, git_otype type, git_filter_list *fl); /* * Hash a `path`, assuming it could be a POSIX symlink: if the path is a diff --git a/src/odb_loose.c b/src/odb_loose.c index 76ed8e232..ced272b33 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -33,6 +33,8 @@ typedef struct loose_backend { int object_zlib_level; /** loose object zlib compression level. */ int fsync_object_files; /** loose object file fsync flag. */ + mode_t object_file_mode; + mode_t object_dir_mode; size_t objects_dirlen; char objects_dir[GIT_FLEX_ARRAY]; @@ -79,7 +81,7 @@ static int object_file_name( static int object_mkdir(const git_buf *name, const loose_backend *be) { return git_futils_mkdir( - name->ptr + be->objects_dirlen, be->objects_dir, GIT_OBJECT_DIR_MODE, + name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } @@ -499,7 +501,7 @@ static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { } if (sstate->found > 1) - return git_odb__error_ambiguous("multiple matches in loose objects"); + return GIT_EAMBIGUOUS; return 0; } @@ -544,13 +546,17 @@ static int locate_object_short_oid( /* Explore directory to find a unique object matching short_oid */ error = git_path_direach( - object_location, fn_locate_object_short_oid, &state); - if (error) + object_location, 0, fn_locate_object_short_oid, &state); + + if (error && error != GIT_EUSER) return error; if (!state.found) return git_odb__error_notfound("no matching loose object for prefix", short_oid); + if (state.found > 1) + return git_odb__error_ambiguous("multiple matches in loose objects"); + /* Convert obtained hex formatted oid to raw */ error = git_oid_fromstr(res_oid, (char *)state.res_oid); if (error) @@ -641,10 +647,12 @@ static int loose_backend__read_prefix( { int error = 0; + assert(len <= GIT_OID_HEXSZ); + if (len < GIT_OID_MINPREFIXLEN) error = git_odb__error_ambiguous("prefix length too short"); - else if (len >= GIT_OID_HEXSZ) { + else if (len == GIT_OID_HEXSZ) { /* We can fall back to regular read method */ error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); if (!error) @@ -739,7 +747,7 @@ static int foreach_cb(void *_state, git_buf *path) { struct foreach_state *state = (struct foreach_state *) _state; - return git_path_direach(path, foreach_object_dir_cb, state); + return git_path_direach(path, 0, foreach_object_dir_cb, state); } static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) @@ -762,34 +770,26 @@ static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb state.data = data; state.dir_len = git_buf_len(&buf); - error = git_path_direach(&buf, foreach_cb, &state); + error = git_path_direach(&buf, 0, foreach_cb, &state); git_buf_free(&buf); return state.cb_error ? state.cb_error : error; } -static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) +static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid) { loose_writestream *stream = (loose_writestream *)_stream; loose_backend *backend = (loose_backend *)_stream->backend; git_buf final_path = GIT_BUF_INIT; int error = 0; - if (git_filebuf_hash(oid, &stream->fbuf) < 0 || - object_file_name(&final_path, backend, oid) < 0 || + if (object_file_name(&final_path, backend, oid) < 0 || object_mkdir(&final_path, backend) < 0) error = -1; - /* - * Don't try to add an existing object to the repository. This - * is what git does and allows us to sidestep the fact that - * we're not allowed to overwrite a read-only file on Windows. - */ - else if (git_path_exists(final_path.ptr) == true) - git_filebuf_cleanup(&stream->fbuf); else error = git_filebuf_commit_at( - &stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); + &stream->fbuf, final_path.ptr); git_buf_free(&final_path); @@ -810,17 +810,6 @@ static void loose_backend__stream_free(git_odb_stream *_stream) git__free(stream); } -static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) -{ - const char *type_str = git_object_type2string(obj_type); - int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); - - assert(len > 0); /* otherwise snprintf() is broken */ - assert(((size_t)len) < n); /* otherwise the caller is broken! */ - - return len+1; -} - static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) { loose_backend *backend; @@ -834,7 +823,7 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ backend = (loose_backend *)_backend; *stream_out = NULL; - hdrlen = format_object_header(hdr, sizeof(hdr), length, type); + hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type); stream = git__calloc(1, sizeof(loose_writestream)); GITERR_CHECK_ALLOC(stream); @@ -848,9 +837,9 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&stream->fbuf, tmp_path.ptr, - GIT_FILEBUF_HASH_CONTENTS | GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 || + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT), + backend->object_file_mode) < 0 || stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) { git_filebuf_cleanup(&stream->fbuf); @@ -863,7 +852,7 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ return !stream ? -1 : 0; } -static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) +static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type) { int error = 0, header_len; git_buf final_path = GIT_BUF_INIT; @@ -874,12 +863,13 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v backend = (loose_backend *)_backend; /* prepare the header for the file */ - header_len = format_object_header(header, sizeof(header), len, type); + header_len = git_odb__format_object_header(header, sizeof(header), len, type); if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&fbuf, final_path.ptr, GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0) + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT), + backend->object_file_mode) < 0) { error = -1; goto cleanup; @@ -890,7 +880,7 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v if (object_file_name(&final_path, backend, oid) < 0 || object_mkdir(&final_path, backend) < 0 || - git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0) + git_filebuf_commit_at(&fbuf, final_path.ptr) < 0) error = -1; cleanup: @@ -913,7 +903,9 @@ int git_odb_backend_loose( git_odb_backend **backend_out, const char *objects_dir, int compression_level, - int do_fsync) + int do_fsync, + unsigned int dir_mode, + unsigned int file_mode) { loose_backend *backend; size_t objects_dirlen; @@ -934,8 +926,16 @@ int git_odb_backend_loose( if (compression_level < 0) compression_level = Z_BEST_SPEED; + if (dir_mode == 0) + dir_mode = GIT_OBJECT_DIR_MODE; + + if (file_mode == 0) + file_mode = GIT_OBJECT_FILE_MODE; + backend->object_zlib_level = compression_level; backend->fsync_object_files = do_fsync; + backend->object_dir_mode = dir_mode; + backend->object_file_mode = file_mode; backend->parent.read = &loose_backend__read; backend->parent.write = &loose_backend__write; diff --git a/src/odb_pack.c b/src/odb_pack.c index eec79259b..fd2ca0fd8 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -29,7 +29,7 @@ struct pack_backend { struct pack_writepack { struct git_odb_writepack parent; - git_indexer_stream *indexer_stream; + git_indexer *indexer; }; /** @@ -259,23 +259,26 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen return git_odb__error_notfound("failed to find pack entry", oid); } -static unsigned pack_entry_find_prefix_inner( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len, - struct git_pack_file *last_found) +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len) { int error; size_t i; - unsigned found = 0; + git_oid found_full_oid = {{0}}; + bool found = false; + struct git_pack_file *last_found = backend->last_found; if (last_found) { error = git_pack_entry_find(e, last_found, short_oid, len); if (error == GIT_EAMBIGUOUS) return error; - if (!error) - found = 1; + if (!error) { + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } } for (i = 0; i < backend->packs.length; ++i) { @@ -289,28 +292,16 @@ static unsigned pack_entry_find_prefix_inner( if (error == GIT_EAMBIGUOUS) return error; if (!error) { - if (++found > 1) - break; + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; backend->last_found = p; } } - return found; -} - -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len) -{ - struct git_pack_file *last_found = backend->last_found; - unsigned int found = pack_entry_find_prefix_inner(e, backend, short_oid, len, last_found); - if (!found) return git_odb__error_notfound("no matching pack entry for prefix", short_oid); - else if (found > 1) - return git_odb__error_ambiguous("found multiple pack entries"); else return 0; } @@ -340,19 +331,20 @@ static int pack_backend__refresh(git_odb_backend *_backend) git_buf_sets(&path, backend->pack_folder); /* reload all packs */ - error = git_path_direach(&path, packfile_load__cb, (void *)backend); + error = git_path_direach(&path, 0, packfile_load__cb, backend); git_buf_free(&path); if (error < 0) - return error; + return -1; git_vector_sort(&backend->packs); return 0; } - -static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid) +static int pack_backend__read_header_internal( + size_t *len_p, git_otype *type_p, + struct git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; int error; @@ -365,7 +357,26 @@ static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct gi return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); } -static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +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( + void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; git_rawobj raw; @@ -382,7 +393,24 @@ static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, return 0; } -static int pack_backend__read_prefix( +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( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -419,9 +447,45 @@ static int pack_backend__read_prefix( 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; } @@ -447,13 +511,13 @@ static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb c return 0; } -static int pack_backend__writepack_add(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats) +static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats) { struct pack_writepack *writepack = (struct pack_writepack *)_writepack; assert(writepack); - return git_indexer_stream_add(writepack->indexer_stream, data, size, stats); + return git_indexer_append(writepack->indexer, data, size, stats); } static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats) @@ -462,7 +526,7 @@ static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, assert(writepack); - return git_indexer_stream_finalize(writepack->indexer_stream, stats); + return git_indexer_commit(writepack->indexer, stats); } static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) @@ -471,12 +535,13 @@ static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) assert(writepack); - git_indexer_stream_free(writepack->indexer_stream); + git_indexer_free(writepack->indexer); git__free(writepack); } static int pack_backend__writepack(struct git_odb_writepack **out, git_odb_backend *_backend, + git_odb *odb, git_transfer_progress_callback progress_cb, void *progress_payload) { @@ -492,14 +557,14 @@ static int pack_backend__writepack(struct git_odb_writepack **out, writepack = git__calloc(1, sizeof(struct pack_writepack)); GITERR_CHECK_ALLOC(writepack); - if (git_indexer_stream_new(&writepack->indexer_stream, - backend->pack_folder, progress_cb, progress_payload) < 0) { + if (git_indexer_new(&writepack->indexer, + backend->pack_folder, 0, odb, progress_cb, progress_payload) < 0) { git__free(writepack); return -1; } writepack->parent.backend = _backend; - writepack->parent.add = pack_backend__writepack_add; + writepack->parent.append = pack_backend__writepack_append; writepack->parent.commit = pack_backend__writepack_commit; writepack->parent.free = pack_backend__writepack_free; diff --git a/src/oid.c b/src/oid.c index 8300e46c1..d56b6af24 100644 --- a/src/oid.c +++ b/src/oid.c @@ -211,7 +211,7 @@ int git_oid_strcmp(const git_oid *oid_a, const char *str) for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) { if ((hexval = git__fromhex(*str++)) < 0) return -1; - strval = hexval << 4; + strval = (unsigned char)(hexval << 4); if (*str) { if ((hexval = git__fromhex(*str++)) < 0) return -1; @@ -369,8 +369,10 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) bool is_leaf; node_index idx; - if (os->full) + if (os->full) { + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); return -1; + } if (text_oid == NULL) return os->min_length; @@ -396,12 +398,19 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) node->tail = NULL; node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); - GITERR_CHECK_ALLOC(node); + if (node == NULL) { + if (os->full) + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); + return -1; + } } if (node->children[c] == 0) { - if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) + if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { + if (os->full) + giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full"); return -1; + } break; } diff --git a/src/oid.h b/src/oid.h index 077d0a4c8..cfe7ca1b2 100644 --- a/src/oid.h +++ b/src/oid.h @@ -9,17 +9,8 @@ #include "git2/oid.h" -/* - * Compare two oid structures. - * - * @param a first oid structure. - * @param b second oid structure. - * @return <0, 0, >0 if a < b, a == b, a > b. - */ -GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - const unsigned char *sha1 = a->id; - const unsigned char *sha2 = b->id; int i; for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) { @@ -30,4 +21,16 @@ GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) return 0; } +/* + * Compare two oid structures. + * + * @param a first oid structure. + * @param b second oid structure. + * @return <0, 0, >0 if a < b, a == b, a > b. + */ +GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +{ + return git_oid__hashcmp(a->id, b->id); +} + #endif diff --git a/src/pack-objects.c b/src/pack-objects.c index 500104c55..2d62507f2 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -14,6 +14,7 @@ #include "pack.h" #include "thread-utils.h" #include "tree.h" +#include "util.h" #include "git2/pack.h" #include "git2/commit.h" @@ -25,7 +26,7 @@ struct unpacked { git_pobject *object; void *data; struct git_delta_index *index; - unsigned int depth; + int depth; }; struct tree_walk_context { @@ -34,7 +35,7 @@ struct tree_walk_context { }; struct pack_write_context { - git_indexer_stream *indexer; + git_indexer *indexer; git_transfer_progress *stats; }; @@ -57,6 +58,9 @@ struct pack_write_context { #define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock) #define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock) +/* The minimal interval between progress updates (in seconds). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 + static unsigned name_hash(const char *name) { unsigned c, hash = 0; @@ -213,41 +217,19 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, kh_value(pb->object_ix, pos) = po; pb->done = false; - return 0; -} -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", - * then three bits of "type", - * with the high bit being "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int gen_pack_object_header( - unsigned char *hdr, - unsigned long size, - git_otype type) -{ - unsigned char *hdr_base; - unsigned char c; - - assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA); - - /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ - - c = (unsigned char)((type << 4) | (size & 15)); - size >>= 4; - hdr_base = hdr; - - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; + if (pb->progress_cb) { + double current_time = git__timer(); + if ((current_time - pb->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) { + pb->last_progress_report_time = current_time; + if (pb->progress_cb(GIT_PACKBUILDER_ADDING_OBJECTS, pb->nr_objects, 0, pb->progress_cb_payload)) { + giterr_clear(); + return GIT_EUSER; + } + } } - *hdr++ = c; - return (int)(hdr - hdr_base); + return 0; } static int get_delta(void **out, git_odb *odb, git_pobject *po) @@ -290,7 +272,7 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po) git_buf zbuf = GIT_BUF_INIT; git_otype type; unsigned char hdr[10]; - unsigned int hdr_len; + size_t hdr_len; unsigned long size; void *data; @@ -311,7 +293,7 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po) } /* Write header */ - hdr_len = gen_pack_object_header(hdr, size, type); + hdr_len = git_packfile__object_header(hdr, size, type); if (git_buf_put(buf, (char *)hdr, hdr_len) < 0) goto on_error; @@ -505,8 +487,10 @@ static git_pobject **compute_write_order(git_packbuilder *pb) /* * Mark objects that are at the tip of tags. */ - if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) + if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { + git__free(wo); return NULL; + } /* * Give the objects in the original recency order until @@ -576,50 +560,52 @@ static int write_pack(git_packbuilder *pb, git_buf buf = GIT_BUF_INIT; enum write_one_status status; struct git_pack_header ph; + git_oid entry_oid; unsigned int i = 0; + int error = 0; write_order = compute_write_order(pb); - if (write_order == NULL) - goto on_error; + if (write_order == NULL) { + error = -1; + goto done; + } /* Write pack header */ ph.hdr_signature = htonl(PACK_SIGNATURE); ph.hdr_version = htonl(PACK_VERSION); ph.hdr_entries = htonl(pb->nr_objects); - if (cb(&ph, sizeof(ph), data) < 0) - goto on_error; + if ((error = cb(&ph, sizeof(ph), data)) < 0) + goto done; - if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0) - goto on_error; + if ((error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) + goto done; pb->nr_remaining = pb->nr_objects; do { pb->nr_written = 0; for ( ; i < pb->nr_objects; ++i) { po = write_order[i]; - if (write_one(&buf, pb, po, &status) < 0) - goto on_error; - if (cb(buf.ptr, buf.size, data) < 0) - goto on_error; + if ((error = write_one(&buf, pb, po, &status)) < 0) + goto done; + if ((error = cb(buf.ptr, buf.size, data)) < 0) + goto done; git_buf_clear(&buf); } pb->nr_remaining -= pb->nr_written; } while (pb->nr_remaining && i < pb->nr_objects); + + if ((error = git_hash_final(&entry_oid, &pb->ctx)) < 0) + goto done; + + error = cb(entry_oid.id, GIT_OID_RAWSZ, data); + +done: git__free(write_order); git_buf_free(&buf); - - if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0) - goto on_error; - - return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data); - -on_error: - git__free(write_order); - git_buf_free(&buf); - return -1; + return error; } static int write_pack_buf(void *buf, size_t size, void *data) @@ -674,7 +660,7 @@ static int delta_cacheable(git_packbuilder *pb, unsigned long src_size, } static int try_delta(git_packbuilder *pb, struct unpacked *trg, - struct unpacked *src, unsigned int max_depth, + struct unpacked *src, int max_depth, unsigned long *mem_usage, int *ret) { git_pobject *trg_object = trg->object; @@ -839,7 +825,7 @@ static unsigned long free_unpacked(struct unpacked *n) static int find_deltas(git_packbuilder *pb, git_pobject **list, unsigned int *list_size, unsigned int window, - unsigned int depth) + int depth) { git_pobject *po; git_buf zbuf = GIT_BUF_INIT; @@ -854,8 +840,7 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, for (;;) { struct unpacked *n = array + idx; - unsigned int max_depth; - int j, best_base = -1; + int max_depth, j, best_base = -1; git_packbuilder__progress_lock(pb); if (!*list_size) { @@ -1048,7 +1033,7 @@ static void *threaded_find_deltas(void *arg) static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, unsigned int list_size, unsigned int window, - unsigned int depth) + int depth) { struct thread_params *p; int i, ret, active_threads = 0; @@ -1205,6 +1190,13 @@ static int prepare_pack(git_packbuilder *pb) if (pb->nr_objects == 0 || pb->done) return 0; /* nothing to do */ + /* + * Although we do not report progress during deltafication, we + * at least report that we are in the deltafication stage + */ + if (pb->progress_cb) + pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); + delta_list = git__malloc(pb->nr_objects * sizeof(*delta_list)); GITERR_CHECK_ALLOC(delta_list); @@ -1250,40 +1242,48 @@ int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) static int write_cb(void *buf, size_t len, void *payload) { struct pack_write_context *ctx = payload; - return git_indexer_stream_add(ctx->indexer, buf, len, ctx->stats); + return git_indexer_append(ctx->indexer, buf, len, ctx->stats); } int git_packbuilder_write( git_packbuilder *pb, const char *path, + unsigned int mode, git_transfer_progress_callback progress_cb, void *progress_cb_payload) { - git_indexer_stream *indexer; + git_indexer *indexer; git_transfer_progress stats; struct pack_write_context ctx; PREPARE_PACK; - if (git_indexer_stream_new( - &indexer, path, progress_cb, progress_cb_payload) < 0) + if (git_indexer_new( + &indexer, path, mode, pb->odb, progress_cb, progress_cb_payload) < 0) return -1; ctx.indexer = indexer; ctx.stats = &stats; if (git_packbuilder_foreach(pb, write_cb, &ctx) < 0 || - git_indexer_stream_finalize(indexer, &stats) < 0) { - git_indexer_stream_free(indexer); + git_indexer_commit(indexer, &stats) < 0) { + git_indexer_free(indexer); return -1; } - git_indexer_stream_free(indexer); + git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer)); + + git_indexer_free(indexer); return 0; } #undef PREPARE_PACK +const git_oid *git_packbuilder_hash(git_packbuilder *pb) +{ + return &pb->pack_oid; +} + static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *payload) { struct tree_walk_context *ctx = payload; @@ -1346,6 +1346,17 @@ uint32_t git_packbuilder_written(git_packbuilder *pb) return pb->nr_written; } +int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload) +{ + if (!pb) + return -1; + + pb->progress_cb = progress_cb; + pb->progress_cb_payload = progress_cb_payload; + + return 0; +} + void git_packbuilder_free(git_packbuilder *pb) { if (pb == NULL) diff --git a/src/pack-objects.h b/src/pack-objects.h index 8e7ba7f78..0c94a5a7a 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -16,6 +16,7 @@ #include "netops.h" #include "git2/oid.h" +#include "git2/pack.h" #define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */ #define GIT_PACK_DEPTH 50 /* max delta depth */ @@ -79,6 +80,10 @@ struct git_packbuilder { int nr_threads; /* nr of threads to use */ + git_packbuilder_progress progress_cb; + void *progress_cb_payload; + double last_progress_report_time; /* the time progress was last reported */ + bool done; }; diff --git a/src/pack.c b/src/pack.c index 7ce7099e0..644b2d465 100644 --- a/src/pack.c +++ b/src/pack.c @@ -329,8 +329,10 @@ static int pack_index_open(struct git_pack_file *p) memcpy(idx_name, p->pack_name, base_len); memcpy(idx_name + base_len, ".idx", sizeof(".idx")); - if ((error = git_mutex_lock(&p->lock)) < 0) + if ((error = git_mutex_lock(&p->lock)) < 0) { + git__free(idx_name); return error; + } if (p->index_version == -1) error = pack_index_check(idx_name, p); @@ -362,6 +364,38 @@ static unsigned char *pack_window_open( return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); } +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", + * then three bits of "type", + * with the high bit being "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type) +{ + unsigned char *hdr_base; + unsigned char c; + + assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA); + + /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ + + c = (unsigned char)((type << 4) | (size & 15)); + size >>= 4; + hdr_base = hdr; + + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + } + *hdr++ = c; + + return (hdr - hdr_base); +} + + static int packfile_unpack_header1( unsigned long *usedp, size_t *sizep, @@ -820,7 +854,7 @@ void git_packfile_free(struct git_pack_file *p) git_mwindow_free_all(&p->mwf); - if (p->mwf.fd != -1) + if (p->mwf.fd >= 0) p_close(p->mwf.fd); pack_index_free(p); @@ -903,7 +937,8 @@ static int packfile_open(struct git_pack_file *p) cleanup: giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name); - p_close(p->mwf.fd); + if (p->mwf.fd >= 0) + p_close(p->mwf.fd); p->mwf.fd = -1; git_mutex_unlock(&p->lock); @@ -1107,8 +1142,11 @@ static int pack_entry_find_offset( short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); #endif - /* Use git.git lookup code */ +#ifdef GIT_USE_LOOKUP pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); +#else + pos = sha1_position(index, stride, lo, hi, short_oid->id); +#endif if (pos >= 0) { /* An object matching exactly the oid was found */ diff --git a/src/pack.h b/src/pack.h index aeeac9ce1..28146ab30 100644 --- a/src/pack.h +++ b/src/pack.h @@ -112,6 +112,8 @@ typedef struct git_packfile_stream { git_mwindow *mw; } git_packfile_stream; +size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type); + int git_packfile_unpack_header( size_t *size_p, git_otype *type_p, diff --git a/src/path.c b/src/path.c index 6437979d5..750dd3ef7 100644 --- a/src/path.c +++ b/src/path.c @@ -8,7 +8,6 @@ #include "path.h" #include "posix.h" #ifdef GIT_WIN32 -#include "win32/dir.h" #include "win32/posix.h" #else #include @@ -243,8 +242,8 @@ int git_path_root(const char *path) #ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ - else if ((path[0] == '/' && path[1] == '/') || - (path[0] == '\\' && path[1] == '\\')) + else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || + (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) { offset += 2; @@ -484,74 +483,90 @@ bool git_path_isfile(const char *path) bool git_path_is_empty_dir(const char *path) { - git_buf pathbuf = GIT_BUF_INIT; HANDLE hFind = INVALID_HANDLE_VALUE; - wchar_t wbuf[GIT_WIN_PATH]; + git_win32_path wbuf; + int wbufsz; WIN32_FIND_DATAW ffd; bool retval = true; - if (!git_path_isdir(path)) return false; + if (!git_path_isdir(path)) + return false; - git_buf_printf(&pathbuf, "%s\\*", path); - git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf)); + wbufsz = git_win32_path_from_c(wbuf, path); + if (!wbufsz || wbufsz + 2 > GIT_WIN_PATH_UTF16) + return false; + memcpy(&wbuf[wbufsz - 1], L"\\*", 3 * sizeof(wchar_t)); hFind = FindFirstFileW(wbuf, &ffd); - if (INVALID_HANDLE_VALUE == hFind) { - giterr_set(GITERR_OS, "Couldn't open '%s'", path); + if (INVALID_HANDLE_VALUE == hFind) return false; - } do { if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { retval = false; + break; } } while (FindNextFileW(hFind, &ffd) != 0); FindClose(hFind); - git_buf_free(&pathbuf); return retval; } #else +static int path_found_entry(void *payload, git_buf *path) +{ + GIT_UNUSED(payload); + return !git_path_is_dot_or_dotdot(path->ptr); +} + bool git_path_is_empty_dir(const char *path) { - DIR *dir = NULL; - struct dirent *e; - bool retval = true; + int error; + git_buf dir = GIT_BUF_INIT; - if (!git_path_isdir(path)) return false; - - dir = opendir(path); - if (!dir) { - giterr_set(GITERR_OS, "Couldn't open '%s'", path); + if (!git_path_isdir(path)) return false; - } - while ((e = readdir(dir)) != NULL) { - if (!git_path_is_dot_or_dotdot(e->d_name)) { - giterr_set(GITERR_INVALID, - "'%s' exists and is not an empty directory", path); - retval = false; - break; - } - } - closedir(dir); + if (!(error = git_buf_sets(&dir, path))) + error = git_path_direach(&dir, 0, path_found_entry, NULL); - return retval; + git_buf_free(&dir); + + return !error; } + #endif +int git_path_set_error(int errno_value, const char *path, const char *action) +{ + switch (errno_value) { + case ENOENT: + case ENOTDIR: + giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action); + return GIT_ENOTFOUND; + + case EINVAL: + case ENAMETOOLONG: + giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path); + return GIT_EINVALIDSPEC; + + case EEXIST: + giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path); + return GIT_EEXISTS; + + default: + giterr_set(GITERR_OS, "Could not %s '%s'", action, path); + return -1; + } +} + int git_path_lstat(const char *path, struct stat *st) { - int err = 0; + if (p_lstat(path, st) == 0) + return 0; - if (p_lstat(path, st) < 0) { - err = (errno == ENOENT) ? GIT_ENOTFOUND : -1; - giterr_set(GITERR_OS, "Failed to stat file '%s'", path); - } - - return err; + return git_path_set_error(errno, path, "stat"); } static bool _check_dir_contents( @@ -564,7 +579,7 @@ static bool _check_dir_contents( size_t sub_size = strlen(sub); /* leave base valid even if we could not make space for subdir */ - if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0) + if (git_buf_try_grow(dir, dir_size + sub_size + 2, false, false) < 0) return false; /* save excursion */ @@ -603,7 +618,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) } /* call dirname if this is not a directory */ - if (!error && git_path_isdir(dir->ptr) == false) + if (!error) /* && git_path_isdir(dir->ptr) == false) */ error = git_path_dirname_r(dir, dir->ptr); if (!error) @@ -645,12 +660,33 @@ int git_path_resolve_relative(git_buf *path, size_t ceiling) /* do nothing with singleton dot */; else if (len == 2 && from[0] == '.' && from[1] == '.') { - while (to > base && to[-1] == '/') to--; - while (to > base && to[-1] != '/') to--; - } + /* error out if trying to up one from a hard base */ + if (to == base && ceiling != 0) { + giterr_set(GITERR_INVALID, + "Cannot strip root component off url"); + return -1; + } - else { - if (*next == '/') + /* no more path segments to strip, + * use '../' as a new base path */ + if (to == base) { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + /* this is now the base, can't back up from a + * relative prefix */ + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + } else { + if (*next == '/' && *from != '/') len++; if (to != from) @@ -702,14 +738,108 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +bool git_path_has_non_ascii(const char *path, size_t pathlen) +{ + const uint8_t *scan = (const uint8_t *)path, *end; + + for (end = scan + pathlen; scan < end; ++scan) + if (*scan & 0x80) + return true; + + return false; +} + +#ifdef GIT_USE_ICONV + +int git_path_iconv_init_precompose(git_path_iconv_t *ic) +{ + git_buf_init(&ic->buf, 0); + ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); + return 0; +} + +void git_path_iconv_clear(git_path_iconv_t *ic) +{ + if (ic) { + if (ic->map != (iconv_t)-1) + iconv_close(ic->map); + git_buf_free(&ic->buf); + } +} + +int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen) +{ + char *nfd = *in, *nfc; + size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv; + int retry = 1; + + if (!ic || ic->map == (iconv_t)-1 || + !git_path_has_non_ascii(*in, *inlen)) + return 0; + + while (1) { + if (git_buf_grow(&ic->buf, wantlen) < 0) + return -1; + + nfc = ic->buf.ptr + ic->buf.size; + nfclen = ic->buf.asize - ic->buf.size; + + rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); + + ic->buf.size = (nfc - ic->buf.ptr); + + if (rv != (size_t)-1) + break; + + if (errno != E2BIG) + goto fail; + + /* make space for 2x the remaining data to be converted + * (with per retry overhead to avoid infinite loops) + */ + wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + + if (retry++ > 4) + goto fail; + } + + ic->buf.ptr[ic->buf.size] = '\0'; + + *in = ic->buf.ptr; + *inlen = ic->buf.size; + + return 0; + +fail: + giterr_set(GITERR_OS, "Unable to convert unicode path data"); + return -1; +} + +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif + int git_path_direach( git_buf *path, + uint32_t flags, int (*fn)(void *, git_buf *), void *arg) { + int error = 0; ssize_t wd_len; DIR *dir; - struct dirent *de, *de_buf; + path_dirent_data de_data; + struct dirent *de, *de_buf = (struct dirent *)&de_data; + + (void)flags; + +#ifdef GIT_USE_ICONV + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif if (git_path_to_dir(path) < 0) return -1; @@ -721,64 +851,80 @@ int git_path_direach( return -1; } -#if defined(__sun) || defined(__GNU__) - de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else - de_buf = git__malloc(sizeof(struct dirent)); +#ifdef GIT_USE_ICONV + if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_path_iconv_init_precompose(&ic); #endif while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { - int result; + char *de_path = de->d_name; + size_t de_len = strlen(de_path); - if (git_path_is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de_path)) continue; - if (git_buf_puts(path, de->d_name) < 0) { - closedir(dir); - git__free(de_buf); - return -1; - } +#ifdef GIT_USE_ICONV + if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0) + break; +#endif + + if ((error = git_buf_put(path, de_path, de_len)) < 0) + break; - result = fn(arg, path); + error = fn(arg, path); git_buf_truncate(path, wd_len); /* restore path */ - if (result < 0) { - closedir(dir); - git__free(de_buf); - return -1; + if (error) { + error = GIT_EUSER; + break; } } closedir(dir); - git__free(de_buf); - return 0; + +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&ic); +#endif + + return error; } int git_path_dirload( const char *path, size_t prefix_len, size_t alloc_extra, + unsigned int flags, git_vector *contents) { int error, need_slash; DIR *dir; - struct dirent *de, *de_buf; size_t path_len; + path_dirent_data de_data; + struct dirent *de, *de_buf = (struct dirent *)&de_data; + + (void)flags; + +#ifdef GIT_USE_ICONV + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + assert(path && contents); - assert(path != NULL && contents != NULL); path_len = strlen(path); - assert(path_len > 0 && path_len >= prefix_len); + if (!path_len || path_len < prefix_len) { + giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path); + return -1; + } if ((dir = opendir(path)) == NULL) { giterr_set(GITERR_OS, "Failed to open directory '%s'", path); return -1; } -#if defined(__sun) || defined(__GNU__) - de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); -#else - de_buf = git__malloc(sizeof(struct dirent)); +#ifdef GIT_USE_ICONV + if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_path_iconv_init_precompose(&ic); #endif path += prefix_len; @@ -786,34 +932,38 @@ int git_path_dirload( need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0; while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) { - char *entry_path; - size_t entry_len; + char *entry_path, *de_path = de->d_name; + size_t alloc_size, de_len = strlen(de_path); - if (git_path_is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de_path)) continue; - entry_len = strlen(de->d_name); +#ifdef GIT_USE_ICONV + if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0) + break; +#endif - entry_path = git__malloc( - path_len + need_slash + entry_len + 1 + alloc_extra); - GITERR_CHECK_ALLOC(entry_path); + alloc_size = path_len + need_slash + de_len + 1 + alloc_extra; + if ((entry_path = git__calloc(alloc_size, 1)) == NULL) { + error = -1; + break; + } if (path_len) memcpy(entry_path, path, path_len); if (need_slash) entry_path[path_len] = '/'; - memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); - entry_path[path_len + need_slash + entry_len] = '\0'; + memcpy(&entry_path[path_len + need_slash], de_path, de_len); - if (git_vector_insert(contents, entry_path) < 0) { - closedir(dir); - git__free(de_buf); - return -1; - } + if ((error = git_vector_insert(contents, entry_path)) < 0) + break; } closedir(dir); - git__free(de_buf); + +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&ic); +#endif if (error != 0) giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); @@ -836,7 +986,7 @@ int git_path_with_stat_cmp_icase(const void *a, const void *b) int git_path_dirload_with_stat( const char *path, size_t prefix_len, - bool ignore_case, + unsigned int flags, const char *start_stat, const char *end_stat, git_vector *contents) @@ -853,13 +1003,14 @@ int git_path_dirload_with_stat( return -1; error = git_path_dirload( - path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents); if (error < 0) { git_buf_free(&full); return error; } - strncomp = ignore_case ? git__strncasecmp : git__strncmp; + strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? + git__strncasecmp : git__strncmp; /* stat struct at start of git_path_with_stat, so shift path text */ git_vector_foreach(contents, i, ps) { @@ -880,8 +1031,16 @@ int git_path_dirload_with_stat( git_buf_truncate(&full, prefix_len); if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || - (error = git_path_lstat(full.ptr, &ps->st)) < 0) + (error = git_path_lstat(full.ptr, &ps->st)) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + git_vector_remove(contents, i--); + continue; + } + break; + } if (S_ISDIR(ps->st.st_mode)) { if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0) diff --git a/src/path.h b/src/path.h index ead4fa338..3daafd265 100644 --- a/src/path.h +++ b/src/path.h @@ -175,7 +175,6 @@ extern bool git_path_contains(git_buf *dir, const char *item); * * @param parent Directory path that might contain subdir * @param subdir Subdirectory name to look for in parent - * @param append_if_exists If true, then subdir will be appended to the parent path if it does exist * @return true if subdirectory exists, false otherwise. */ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); @@ -185,7 +184,6 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); * * @param dir Directory path that might contain file * @param file File name to look for in parent - * @param append_if_exists If true, then file will be appended to the path if it does exist * @return true if file exists, false otherwise. */ extern bool git_path_contains_file(git_buf *dir, const char *file); @@ -244,21 +242,28 @@ extern int git_path_resolve_relative(git_buf *path, size_t ceiling); */ extern int git_path_apply_relative(git_buf *target, const char *relpath); +enum { + GIT_PATH_DIR_IGNORE_CASE = (1u << 0), + GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), +}; + /** * Walk each directory entry, except '.' and '..', calling fn(state). * - * @param pathbuf buffer the function reads the initial directory + * @param pathbuf Buffer the function reads the initial directory * path from, and updates with each successive entry's name. - * @param fn function to invoke with each entry. The first arg is - * the input state and the second arg is pathbuf. The function - * may modify the pathbuf, but only by appending new text. - * @param state to pass to fn as the first arg. + * @param flags Combination of GIT_PATH_DIR flags. + * @param callback Callback for each entry. Passed the `payload` and each + * successive path inside the directory as a full path. This may + * safely append text to the pathbuf if needed. + * @param payload Passed to callback as first argument. * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ extern int git_path_direach( git_buf *pathbuf, - int (*fn)(void *, git_buf *), - void *state); + uint32_t flags, + int (*callback)(void *payload, git_buf *path), + void *payload); /** * Sort function to order two paths @@ -278,19 +283,19 @@ extern int git_path_cmp( * @param pathbuf Buffer the function reads the directory from and * and updates with each successive name. * @param ceiling Prefix of path at which to stop walking up. If NULL, - * this will walk all the way up to the root. If not a prefix of - * pathbuf, the callback will be invoked a single time on the - * original input path. - * @param fn Function to invoke on each path. The first arg is the - * input satte and the second arg is the pathbuf. The function - * should not modify the pathbuf. + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param callback Function to invoke on each path. Passed the `payload` + * and the buffer containing the current path. The path should not + * be modified in any way. * @param state Passed to fn as the first ath. */ extern int git_path_walk_up( git_buf *pathbuf, const char *ceiling, - int (*fn)(void *state, git_buf *), - void *state); + int (*callback)(void *payload, git_buf *path), + void *payload); /** * Load all directory entries (except '.' and '..') into a vector. @@ -306,12 +311,14 @@ extern int git_path_walk_up( * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. * @param alloc_extra Extra bytes to add to each string allocation in * case you want to append anything funny. + * @param flags Combination of GIT_PATH_DIR flags. * @param contents Vector to fill with directory entry names. */ extern int git_path_dirload( const char *path, size_t prefix_len, size_t alloc_extra, + uint32_t flags, git_vector *contents); @@ -338,7 +345,7 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b); * * @param path The directory to read from * @param prefix_len The trailing part of path to prefix to entry paths - * @param ignore_case How to sort and compare paths with start/end limits + * @param flags GIT_PATH_DIR flags from above * @param start_stat As optimization, only stat values after this prefix * @param end_stat As optimization, only stat values before this prefix * @param contents Vector to fill with git_path_with_stat structures @@ -346,9 +353,78 @@ extern int git_path_with_stat_cmp_icase(const void *a, const void *b); extern int git_path_dirload_with_stat( const char *path, size_t prefix_len, - bool ignore_case, + uint32_t flags, const char *start_stat, const char *end_stat, git_vector *contents); +enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 }; + +/* + * Determines if a path is equal to or potentially a child of another. + * @param parent The possible parent + * @param child The possible child + */ +GIT_INLINE(int) git_path_equal_or_prefixed( + const char *parent, + const char *child) +{ + const char *p = parent, *c = child; + + while (*p && *c) { + if (*p++ != *c++) + return GIT_PATH_NOTEQUAL; + } + + if (*p != '\0') + return GIT_PATH_NOTEQUAL; + if (*c == '\0') + return GIT_PATH_EQUAL; + if (*c == '/') + return GIT_PATH_PREFIX; + + return GIT_PATH_NOTEQUAL; +} + +/* translate errno to libgit2 error code and set error message */ +extern int git_path_set_error( + int errno_value, const char *path, const char *action); + +/* check if non-ascii characters are present in filename */ +extern bool git_path_has_non_ascii(const char *path, size_t pathlen); + +#define GIT_PATH_REPO_ENCODING "UTF-8" + +#ifdef __APPLE__ +#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" +#else +#define GIT_PATH_NATIVE_ENCODING "UTF-8" +#endif + +#ifdef GIT_USE_ICONV + +#include + +typedef struct { + iconv_t map; + git_buf buf; +} git_path_iconv_t; + +#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_BUF_INIT } + +/* Init iconv data for converting decomposed UTF-8 to precomposed */ +extern int git_path_iconv_init_precompose(git_path_iconv_t *ic); + +/* Clear allocated iconv data */ +extern void git_path_iconv_clear(git_path_iconv_t *ic); + +/* + * Rewrite `in` buffer using iconv map if necessary, replacing `in` + * pointer internal iconv buffer if rewrite happened. The `in` pointer + * will be left unchanged if no rewrite was needed. + */ +extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); + +#endif /* GIT_USE_ICONV */ + #endif diff --git a/src/pathspec.c b/src/pathspec.c index f029836d0..1e7e65e90 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -5,9 +5,16 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "git2/pathspec.h" +#include "git2/diff.h" #include "pathspec.h" #include "buf_text.h" #include "attr_file.h" +#include "iterator.h" +#include "repository.h" +#include "index.h" +#include "bitvec.h" +#include "diff.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -56,7 +63,7 @@ bool git_pathspec_is_empty(const git_strarray *pathspec) } /* build a vector of fnmatch patterns to evaluate efficiently */ -int git_pathspec_init( +int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool) { size_t i; @@ -76,7 +83,7 @@ int git_pathspec_init( if (!match) return -1; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); if (ret == GIT_ENOTFOUND) { @@ -93,7 +100,7 @@ int git_pathspec_init( } /* free data from the pathspec vector */ -void git_pathspec_free(git_vector *vspec) +void git_pathspec__vfree(git_vector *vspec) { git_attr_fnmatch *match; unsigned int i; @@ -106,88 +113,612 @@ void git_pathspec_free(git_vector *vspec) git_vector_free(vspec); } +struct pathspec_match_context { + int fnmatch_flags; + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); +}; + +static void pathspec_match_context_init( + struct pathspec_match_context *ctxt, + bool disable_fnmatch, + bool casefold) +{ + if (disable_fnmatch) + ctxt->fnmatch_flags = -1; + else if (casefold) + ctxt->fnmatch_flags = FNM_CASEFOLD; + else + ctxt->fnmatch_flags = 0; + + if (casefold) { + ctxt->strcomp = git__strcasecmp; + ctxt->strncomp = git__strncasecmp; + } else { + ctxt->strcomp = git__strcmp; + ctxt->strncomp = git__strncmp; + } +} + +static int pathspec_match_one( + const git_attr_fnmatch *match, + struct pathspec_match_context *ctxt, + const char *path) +{ + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + + if (result == FNM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0; + + if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH) + result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + ctxt->strncomp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + /* if we didn't match and this is a negative match, check for exact + * match of filename with leading '!' + */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && + *path == '!' && + ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && + (!path[match->length + 1] || path[match->length + 1] == '/')) + return 1; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; + return -1; +} + +static int git_pathspec__match_at( + size_t *matched_at, + const git_vector *vspec, + struct pathspec_match_context *ctxt, + const char *path0, + const char *path1) +{ + int result = GIT_ENOTFOUND; + size_t i = 0; + const git_attr_fnmatch *match; + + git_vector_foreach(vspec, i, match) { + if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) + break; + if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) + break; + } + + *matched_at = i; + return result; +} + /* match a path against the vectorized pathspec */ -bool git_pathspec_match_path( - git_vector *vspec, +bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec) + const char **matched_pathspec, + size_t *matched_at) { - size_t i; - git_attr_fnmatch *match; - int fnmatch_flags = 0; - int (*use_strcmp)(const char *, const char *); - int (*use_strncmp)(const char *, const char *, size_t); + int result; + size_t pos; + struct pathspec_match_context ctxt; if (matched_pathspec) *matched_pathspec = NULL; + if (matched_at) + *matched_at = GIT_PATHSPEC_NOMATCH; if (!vspec || !vspec->length) return true; - if (disable_fnmatch) - fnmatch_flags = -1; - else if (casefold) - fnmatch_flags = FNM_CASEFOLD; + pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); - if (casefold) { - use_strcmp = git__strcasecmp; - use_strncmp = git__strncasecmp; - } else { - use_strcmp = git__strcmp; - use_strncmp = git__strncmp; - } - - git_vector_foreach(vspec, i, match) { - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; - - if (result == FNM_NOMATCH) - result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - - if (fnmatch_flags >= 0 && result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, fnmatch_flags); - - /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - use_strncmp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - if (result == 0) { - if (matched_pathspec) - *matched_pathspec = match->pattern; - - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); + if (result >= 0) { + if (matched_pathspec) { + const git_attr_fnmatch *match = git_vector_get(vspec, pos); + *matched_pathspec = match->pattern; } + + if (matched_at) + *matched_at = pos; } - return false; + return (result > 0); } -int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths) +int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) { int error = 0; - memset(ctxt, 0, sizeof(*ctxt)); + memset(ps, 0, sizeof(*ps)); - ctxt->prefix = git_pathspec_prefix(paths); + ps->prefix = git_pathspec_prefix(paths); - if ((error = git_pool_init(&ctxt->pool, 1, 0)) < 0 || - (error = git_pathspec_init(&ctxt->pathspec, paths, &ctxt->pool)) < 0) - git_pathspec_context_free(ctxt); + if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 || + (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + git_pathspec__clear(ps); return error; } -void git_pathspec_context_free( - git_pathspec_context *ctxt) +void git_pathspec__clear(git_pathspec *ps) { - git__free(ctxt->prefix); - git_pathspec_free(&ctxt->pathspec); - git_pool_clear(&ctxt->pool); - memset(ctxt, 0, sizeof(*ctxt)); + git__free(ps->prefix); + git_pathspec__vfree(&ps->pathspec); + git_pool_clear(&ps->pool); + memset(ps, 0, sizeof(*ps)); } + +int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) +{ + int error = 0; + git_pathspec *ps = git__malloc(sizeof(git_pathspec)); + GITERR_CHECK_ALLOC(ps); + + if ((error = git_pathspec__init(ps, pathspec)) < 0) { + git__free(ps); + return error; + } + + GIT_REFCOUNT_INC(ps); + *out = ps; + return 0; +} + +static void pathspec_free(git_pathspec *ps) +{ + git_pathspec__clear(ps); + git__free(ps); +} + +void git_pathspec_free(git_pathspec *ps) +{ + if (!ps) + return; + GIT_REFCOUNT_DEC(ps, pathspec_free); +} + +int git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path) +{ + bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; + bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; + + assert(ps && path); + + return (0 != git_pathspec__match( + &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); +} + +static void pathspec_match_free(git_pathspec_match_list *m) +{ + git_pathspec_free(m->pathspec); + m->pathspec = NULL; + + git_array_clear(m->matches); + git_array_clear(m->failures); + git_pool_clear(&m->pool); + git__free(m); +} + +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; + } + + /* 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 + */ + GIT_REFCOUNT_INC(ps); + m->pathspec = ps; + m->datatype = datatype; + + return m; +} + +GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) +{ + if (!git_bitvec_get(used, pos)) { + git_bitvec_set(used, pos, true); + return 1; + } + + return 0; +} + +static size_t pathspec_mark_remaining( + git_bitvec *used, + git_vector *patterns, + struct pathspec_match_context *ctxt, + size_t start, + const char *path0, + const char *path1) +{ + size_t count = 0; + + if (path1 == path0) + path1 = NULL; + + for (; start < patterns->length; ++start) { + const git_attr_fnmatch *pat = git_vector_get(patterns, start); + + if (git_bitvec_get(used, start)) + continue; + + if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) + count += pathspec_mark_pattern(used, start); + else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) + count += pathspec_mark_pattern(used, start); + } + + return count; +} + +static int pathspec_build_failure_array( + git_pathspec_string_array_t *failures, + git_vector *patterns, + git_bitvec *used, + git_pool *pool) +{ + size_t pos; + char **failed; + const git_attr_fnmatch *pat; + + for (pos = 0; pos < patterns->length; ++pos) { + if (git_bitvec_get(used, pos)) + continue; + + if ((failed = git_array_alloc(*failures)) == NULL) + return -1; + + pat = git_vector_get(patterns, pos); + + if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) + return -1; + } + + return 0; +} + +static int pathspec_match_from_iterator( + git_pathspec_match_list **out, + git_iterator *iter, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + const git_index_entry *entry = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t pos, used_ct = 0, found_files = 0; + git_index *index = NULL; + git_bitvec used_patterns; + char **file; + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); + GITERR_CHECK_ALLOC(m); + } + + if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) + goto done; + + if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && + (error = git_repository_index__weakptr( + &index, git_iterator_owner(iter))) < 0) + goto done; + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_iterator_ignore_case(iter)); + + while (!(error = git_iterator_advance(&entry, iter))) { + /* search for match with entry->path */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, entry->path, NULL); + + /* no matches for this path */ + if (result < 0) + continue; + + /* if result was a negative pattern match, then don't list file */ + if (!result) { + used_ct += pathspec_mark_pattern(&used_patterns, pos); + continue; + } + + /* check if path is ignored and untracked */ + if (index != NULL && + git_iterator_current_is_ignored(iter) && + git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + ++found_files; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched path into matches array */ + if ((file = (char **)git_array_alloc(m->matches)) == NULL || + (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { + error = -1; + goto done; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto done; + error = 0; + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { + giterr_set(GITERR_INVALID, "No matching files were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) +{ + git_iterator_flag_t f = 0; + + if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) + f |= GIT_ITERATOR_IGNORE_CASE; + else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) + f |= GIT_ITERATOR_DONT_IGNORE_CASE; + + return f; +} + +int git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(repo); + + if (!(error = git_iterator_for_workdir( + &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(index); + + if (!(error = git_iterator_for_index( + &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(tree); + + if (!(error = git_iterator_for_tree( + &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff *diff, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t i, pos, used_ct = 0, found_deltas = 0; + const git_diff_delta *delta, **match; + git_bitvec used_patterns; + + assert(diff); + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); + GITERR_CHECK_ALLOC(m); + } + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_diff_is_sorted_icase(diff)); + + git_vector_foreach(&diff->deltas, i, delta) { + /* search for match with delta */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); + + /* no matches for this path */ + if (result < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + + /* if result was a negative pattern match, then don't list file */ + if (!result) + continue; + + ++found_deltas; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, + delta->old_file.path, delta->new_file.path); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched delta into matches array */ + if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { + error = -1; + goto done; + } else { + *match = delta; + } + } + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { + giterr_set(GITERR_INVALID, "No matching deltas were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +void git_pathspec_match_list_free(git_pathspec_match_list *m) +{ + if (m) + pathspec_match_free(m); +} + +size_t git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->matches) : 0; +} + +const char *git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const char **)git_array_get(m->matches, pos)); +} + +const git_diff_delta *git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const git_diff_delta **)git_array_get(m->matches, pos)); +} + +size_t git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->failures) : 0; +} + +const char * git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = m ? git_array_get(m->failures, pos) : NULL; + + return entry ? *entry : NULL; +} + diff --git a/src/pathspec.h b/src/pathspec.h index f6509df4c..40cd21c3f 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -8,9 +8,35 @@ #define INCLUDE_pathspec_h__ #include "common.h" +#include #include "buffer.h" #include "vector.h" #include "pool.h" +#include "array.h" + +/* public compiled pathspec */ +struct git_pathspec { + git_refcount rc; + char *prefix; + git_vector pathspec; + git_pool pool; +}; + +enum { + PATHSPEC_DATATYPE_STRINGS = 0, + PATHSPEC_DATATYPE_DIFF = 1, +}; + +typedef git_array_t(char *) git_pathspec_string_array_t; + +/* public interface to pathspec matching */ +struct git_pathspec_match_list { + git_pathspec *pathspec; + git_array_t(void *) matches; + git_pathspec_string_array_t failures; + git_pool pool; + int datatype; +}; /* what is the common non-wildcard prefix for all items in the pathspec */ extern char *git_pathspec_prefix(const git_strarray *pathspec); @@ -19,36 +45,31 @@ extern char *git_pathspec_prefix(const git_strarray *pathspec); extern bool git_pathspec_is_empty(const git_strarray *pathspec); /* build a vector of fnmatch patterns to evaluate efficiently */ -extern int git_pathspec_init( +extern int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool); /* free data from the pathspec vector */ -extern void git_pathspec_free(git_vector *vspec); +extern void git_pathspec__vfree(git_vector *vspec); + +#define GIT_PATHSPEC_NOMATCH ((size_t)-1) /* * Match a path against the vectorized pathspec. * The matched pathspec is passed back into the `matched_pathspec` parameter, * unless it is passed as NULL by the caller. */ -extern bool git_pathspec_match_path( - git_vector *vspec, +extern bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec); + const char **matched_pathspec, + size_t *matched_at); /* easy pathspec setup */ -typedef struct { - char *prefix; - git_vector pathspec; - git_pool pool; -} git_pathspec_context; +extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); -extern int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths); - -extern void git_pathspec_context_free( - git_pathspec_context *ctxt); +extern void git_pathspec__clear(git_pathspec *ps); #endif diff --git a/src/posix.h b/src/posix.h index 40bcc1ab0..14c92b93d 100644 --- a/src/posix.h +++ b/src/posix.h @@ -39,7 +39,7 @@ typedef int git_file; * * Some of the methods are slightly wrapped to provide * saner defaults. Some of these methods are emulated - * in Windows platforns. + * in Windows platforms. * * Use your manpages to check the docs on these. */ @@ -71,16 +71,12 @@ typedef int GIT_SOCKET; #define p_localtime_r localtime_r #define p_gmtime_r gmtime_r -#define p_gettimeofday gettimeofday #else typedef SOCKET GIT_SOCKET; -struct timezone; extern struct tm * p_localtime_r (const time_t *timer, struct tm *result); extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result); -extern int p_gettimeofday(struct timeval *tv, struct timezone *tz); - #endif @@ -93,6 +89,10 @@ extern int p_gettimeofday(struct timeval *tv, struct timezone *tz); # include "unix/posix.h" #endif +#ifndef __MINGW32__ +# define p_strnlen strnlen +#endif + #ifdef NO_READDIR_R # include GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) diff --git a/src/pqueue.h b/src/pqueue.h index ed7139285..9061f8279 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -48,7 +48,7 @@ typedef struct { * should be preallocated * @param cmppri the callback function to compare two nodes of the queue * - * @Return the handle or NULL for insufficent memory + * @return the handle or NULL for insufficent memory */ int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri); @@ -83,8 +83,7 @@ int git_pqueue_insert(git_pqueue *q, void *d); /** * pop the highest-ranking item from the queue. - * @param p the queue - * @param d where to copy the entry to + * @param q the queue * @return NULL on error, otherwise the entry */ void *git_pqueue_pop(git_pqueue *q); @@ -93,7 +92,6 @@ void *git_pqueue_pop(git_pqueue *q); /** * access highest-ranking item without removing it. * @param q the queue - * @param d the entry * @return NULL on error, otherwise the entry */ void *git_pqueue_peek(git_pqueue *q); diff --git a/src/push.c b/src/push.c index 452d71789..3c9d5bb35 100644 --- a/src/push.c +++ b/src/push.c @@ -70,6 +70,25 @@ int git_push_set_options(git_push *push, const git_push_options *opts) return 0; } +int git_push_set_callbacks( + git_push *push, + git_packbuilder_progress pack_progress_cb, + void *pack_progress_cb_payload, + git_push_transfer_progress transfer_progress_cb, + void *transfer_progress_cb_payload) +{ + if (!push) + return -1; + + push->pack_progress_cb = pack_progress_cb; + push->pack_progress_cb_payload = pack_progress_cb_payload; + + push->transfer_progress_cb = transfer_progress_cb; + push->transfer_progress_cb_payload = transfer_progress_cb_payload; + + return 0; +} + static void free_refspec(push_spec *spec) { if (spec == NULL) @@ -233,6 +252,37 @@ on_error: return error; } +/** + * Insert all tags until we find a non-tag object, which is returned + * in `out`. + */ +static int enqueue_tag(git_object **out, git_push *push, git_oid *id) +{ + git_object *obj = NULL, *target = NULL; + int error; + + if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJ_TAG)) < 0) + return error; + + while (git_object_type(obj) == GIT_OBJ_TAG) { + if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) + break; + + if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) + break; + + git_object_free(obj); + obj = target; + } + + if (error < 0) + git_object_free(obj); + else + *out = obj; + + return error; +} + static int revwalk(git_vector *commits, git_push *push) { git_remote_head *head; @@ -265,21 +315,11 @@ static int revwalk(git_vector *commits, git_push *push) goto on_error; if (type == GIT_OBJ_TAG) { - git_tag *tag; git_object *target; - if (git_packbuilder_insert(push->pb, &spec->loid, NULL) < 0) + if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) goto on_error; - if (git_tag_lookup(&tag, push->repo, &spec->loid) < 0) - goto on_error; - - if (git_tag_peel(&target, tag) < 0) { - git_tag_free(tag); - goto on_error; - } - git_tag_free(tag); - if (git_object_type(target) == GIT_OBJ_COMMIT) { if (git_revwalk_push(rw, git_object_id(target)) < 0) { git_object_free(target); @@ -542,7 +582,7 @@ static int calculate_work(git_push *push) static int do_push(git_push *push) { - int error; + int error = 0; git_transport *transport = push->remote->transport; if (!transport->push) { @@ -562,28 +602,36 @@ static int do_push(git_push *push) git_packbuilder_set_threads(push->pb, push->pb_parallelism); + if (push->pack_progress_cb) + if ((error = git_packbuilder_set_callbacks(push->pb, push->pack_progress_cb, push->pack_progress_cb_payload)) < 0) + goto on_error; + if ((error = calculate_work(push)) < 0 || (error = queue_objects(push)) < 0 || (error = transport->push(transport, push)) < 0) goto on_error; - error = 0; - on_error: git_packbuilder_free(push->pb); return error; } -static int cb_filter_refs(git_remote_head *ref, void *data) -{ - git_remote *remote = (git_remote *) data; - return git_vector_insert(&remote->refs, ref); -} - static int filter_refs(git_remote *remote) { + const git_remote_head **heads; + size_t heads_len, i; + git_vector_clear(&remote->refs); - return git_remote_ls(remote, cb_filter_refs, remote); + + if (git_remote_ls(&heads, &heads_len, remote) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0) + return -1; + } + + return 0; } int git_push_finish(git_push *push) diff --git a/src/push.h b/src/push.h index e982b8385..6c8bf7229 100644 --- a/src/push.h +++ b/src/push.h @@ -39,6 +39,11 @@ struct git_push { /* options */ unsigned pb_parallelism; + + git_packbuilder_progress pack_progress_cb; + void *pack_progress_cb_payload; + git_push_transfer_progress transfer_progress_cb; + void *transfer_progress_cb_payload; }; /** diff --git a/src/refdb.c b/src/refdb.c index 4de7188b2..adb58806e 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -16,6 +16,7 @@ #include "hash.h" #include "refdb.h" #include "refs.h" +#include "reflog.h" int git_refdb_new(git_refdb **out, git_repository *repo) { @@ -201,5 +202,20 @@ int git_refdb_rename( int git_refdb_delete(struct git_refdb *db, const char *ref_name) { assert(db && db->backend); - return db->backend->delete(db->backend, ref_name); + return db->backend->del(db->backend, ref_name); +} + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) +{ + int error; + + assert(db && db->backend); + + if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; } diff --git a/src/refdb.h b/src/refdb.h index 3aea37b62..0ee60d911 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -43,4 +43,8 @@ void git_refdb_iterator_free(git_reference_iterator *iter); int git_refdb_write(git_refdb *refdb, git_reference *ref, int force); int git_refdb_delete(git_refdb *refdb, const char *ref_name); +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); +int git_refdb_reflog_write(git_reflog *reflog); + + #endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c index b9e283ac5..62d5c1047 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -15,12 +15,15 @@ #include "refdb.h" #include "refdb_fs.h" #include "iterator.h" +#include "sortedcache.h" +#include "signature.h" #include #include #include #include #include +#include GIT__USE_STRMAP; @@ -53,243 +56,151 @@ typedef struct refdb_fs_backend { git_repository *repo; char *path; - git_refcache refcache; + git_sortedcache *refcache; int peeling_mode; + git_iterator_flag_t iterator_flags; + uint32_t direach_flags; } refdb_fs_backend; -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated) +static int packref_cmp(const void *a_, const void *b_) { - git_buf path = GIT_BUF_INIT; - int result; - - assert(file_content && repo_path && ref_name); - - /* Determine the full path of the file */ - if (git_buf_joinpath(&path, repo_path, ref_name) < 0) - return -1; - - result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated); - git_buf_free(&path); - - return result; + const struct packref *a = a_, *b = b_; + return strcmp(a->name, b->name); } -static int packed_parse_oid( - struct packref **ref_out, - const char **buffer_out, - const char *buffer_end) +static int packed_reload(refdb_fs_backend *backend) { - struct packref *ref = NULL; + int error; + git_buf packedrefs = GIT_BUF_INIT; + char *scan, *eof, *eol; - const char *buffer = *buffer_out; - const char *refname_begin, *refname_end; - - size_t refname_len; - git_oid id; - - refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || refname_begin[-1] != ' ') - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&id, buffer) < 0) - goto corrupt; - - refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) - refname_end = buffer_end; - - if (refname_end[-1] == '\r') - refname_end--; - - refname_len = refname_end - refname_begin; - - ref = git__calloc(1, sizeof(struct packref) + refname_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, refname_begin, refname_len); - ref->name[refname_len] = 0; - - git_oid_cpy(&ref->oid, &id); - - *ref_out = ref; - *buffer_out = refname_end + 1; - return 0; - -corrupt: - git__free(ref); - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_parse_peel( - struct packref *tag_ref, - const char **buffer_out, - const char *buffer_end) -{ - const char *buffer = *buffer_out + 1; - - assert(buffer[-1] == '^'); - - /* Ensure it's not the first entry of the file */ - if (tag_ref == NULL) - goto corrupt; - - if (buffer + GIT_OID_HEXSZ > buffer_end) - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) - goto corrupt; - - buffer = buffer + GIT_OID_HEXSZ; - if (*buffer == '\r') - buffer++; - - if (buffer != buffer_end) { - if (*buffer == '\n') - buffer++; - else - goto corrupt; - } - - tag_ref->flags |= PACKREF_HAS_PEEL; - *buffer_out = buffer; - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_load(refdb_fs_backend *backend) -{ - int result, updated; - git_buf packfile = GIT_BUF_INIT; - const char *buffer_start, *buffer_end; - git_refcache *ref_cache = &backend->refcache; - - /* First we make sure we have allocated the hash table */ - if (ref_cache->packfile == NULL) { - ref_cache->packfile = git_strmap_alloc(); - GITERR_CHECK_ALLOC(ref_cache->packfile); - } - - if (backend->path == NULL) + if (!backend->path) return 0; - result = reference_read(&packfile, &ref_cache->packfile_time, - backend->path, GIT_PACKEDREFS_FILE, &updated); + error = git_sortedcache_lockandload(backend->refcache, &packedrefs); /* - * If we couldn't find the file, we need to clear the table and - * return. On any other error, we return that error. If everything - * went fine and the file wasn't updated, then there's nothing new - * for us here, so just return. Anything else means we need to - * refresh the packed refs. + * If we can't find the packed-refs, clear table and return. + * Any other error just gets passed through. + * If no error, and file wasn't changed, just return. + * Anything else means we need to refresh the packed refs. */ - if (result == GIT_ENOTFOUND) { - git_strmap_clear(ref_cache->packfile); - return 0; + if (error <= 0) { + if (error == GIT_ENOTFOUND) { + git_sortedcache_clear(backend->refcache, true); + giterr_clear(); + error = 0; + } + return error; } - if (result < 0) - return -1; + /* At this point, refresh the packed refs from the loaded buffer. */ - if (!updated) - return 0; + git_sortedcache_clear(backend->refcache, false); - /* - * At this point, we want to refresh the packed refs. We already - * have the contents in our buffer. - */ - git_strmap_clear(ref_cache->packfile); - - buffer_start = (const char *)packfile.ptr; - buffer_end = (const char *)(buffer_start) + packfile.size; + scan = (char *)packedrefs.ptr; + eof = scan + packedrefs.size; backend->peeling_mode = PEELING_NONE; - if (buffer_start[0] == '#') { + if (*scan == '#') { static const char *traits_header = "# pack-refs with: "; - if (git__prefixcmp(buffer_start, traits_header) == 0) { - char *traits = (char *)buffer_start + strlen(traits_header); - char *traits_end = strchr(traits, '\n'); + if (git__prefixcmp(scan, traits_header) == 0) { + scan += strlen(traits_header); + eol = strchr(scan, '\n'); - if (traits_end == NULL) + if (!eol) goto parse_failed; + *eol = '\0'; - *traits_end = '\0'; - - if (strstr(traits, " fully-peeled ") != NULL) { + if (strstr(scan, " fully-peeled ") != NULL) { backend->peeling_mode = PEELING_FULL; - } else if (strstr(traits, " peeled ") != NULL) { + } else if (strstr(scan, " peeled ") != NULL) { backend->peeling_mode = PEELING_STANDARD; } - buffer_start = traits_end + 1; + scan = eol + 1; } } - while (buffer_start < buffer_end && buffer_start[0] == '#') { - buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) + while (scan < eof && *scan == '#') { + if (!(eol = strchr(scan, '\n'))) goto parse_failed; - - buffer_start++; + scan = eol + 1; } - while (buffer_start < buffer_end) { - int err; - struct packref *ref = NULL; + while (scan < eof) { + struct packref *ref; + git_oid oid; - if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + /* parse " \n" */ + + if (git_oid_fromstr(&oid, scan) < 0) goto parse_failed; + scan += GIT_OID_HEXSZ; - if (buffer_start[0] == '^') { - if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + if (*scan++ != ' ') + goto parse_failed; + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + *eol = '\0'; + if (eol[-1] == '\r') + eol[-1] = '\0'; + + if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) + goto parse_failed; + scan = eol + 1; + + git_oid_cpy(&ref->oid, &oid); + + /* look for optional "^\n" */ + + if (*scan == '^') { + if (git_oid_fromstr(&oid, scan + 1) < 0) goto parse_failed; - } else if (backend->peeling_mode == PEELING_FULL || - (backend->peeling_mode == PEELING_STANDARD && - git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) { - ref->flags |= PACKREF_CANNOT_PEEL; - } + scan += GIT_OID_HEXSZ + 1; - git_strmap_insert(ref_cache->packfile, ref->name, ref, err); - if (err < 0) - goto parse_failed; + if (scan < eof) { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + git_oid_cpy(&ref->peel, &oid); + ref->flags |= PACKREF_HAS_PEEL; + } + else if (backend->peeling_mode == PEELING_FULL || + (backend->peeling_mode == PEELING_STANDARD && + git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) + ref->flags |= PACKREF_CANNOT_PEEL; } - git_buf_free(&packfile); + git_sortedcache_wunlock(backend->refcache); + git_buf_free(&packedrefs); + return 0; parse_failed: - git_strmap_free(ref_cache->packfile); - ref_cache->packfile = NULL; - git_buf_free(&packfile); + giterr_set(GITERR_REFERENCE, "Corrupted packed references file"); + + git_sortedcache_clear(backend->refcache, false); + git_sortedcache_wunlock(backend->refcache); + git_buf_free(&packedrefs); + return -1; } -static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content) +static int loose_parse_oid( + git_oid *oid, const char *filename, git_buf *file_content) { - size_t len; - const char *str; + const char *str = git_buf_cstr(file_content); - len = git_buf_len(file_content); - if (len < GIT_OID_HEXSZ) + if (git_buf_len(file_content) < GIT_OID_HEXSZ) goto corrupted; - /* str is guranteed to be zero-terminated */ - str = git_buf_cstr(file_content); - /* we need to get 40 OID characters from the file */ - if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) + if (git_oid_fromstr(oid, str) < 0) goto corrupted; /* If the file is longer than 40 chars, the 41st must be a space */ @@ -302,68 +213,72 @@ corrupted: return -1; } -static int loose_lookup_to_packfile( - struct packref **ref_out, - refdb_fs_backend *backend, - const char *name) +static int loose_readbuffer(git_buf *buf, const char *base, const char *path) { - git_buf ref_file = GIT_BUF_INIT; - struct packref *ref = NULL; - size_t name_len; + int error; - *ref_out = NULL; + /* build full path to file */ + if ((error = git_buf_joinpath(buf, base, path)) < 0 || + (error = git_futils_readbuffer(buf, buf->ptr)) < 0) + git_buf_free(buf); - if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0) - return -1; - - git_buf_rtrim(&ref_file); - - name_len = strlen(name); - ref = git__calloc(1, sizeof(struct packref) + name_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, name, name_len); - ref->name[name_len] = 0; - - if (loose_parse_oid(&ref->oid, name, &ref_file) < 0) { - git_buf_free(&ref_file); - git__free(ref); - return -1; - } - - ref->flags = PACKREF_WAS_LOOSE; - - *ref_out = ref; - git_buf_free(&ref_file); - return 0; + return error; } +static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) +{ + int error = 0; + git_buf ref_file = GIT_BUF_INIT; + struct packref *ref = NULL; + git_oid oid; + + /* if we fail to load the loose reference, assume someone changed + * the filesystem under us and skip it... + */ + if (loose_readbuffer(&ref_file, backend->path, name) < 0) { + giterr_clear(); + goto done; + } + + /* skip symbolic refs */ + if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF)) + goto done; + + /* parse OID from file */ + if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) + goto done; + + git_sortedcache_wlock(backend->refcache); + + if (!(error = git_sortedcache_upsert( + (void **)&ref, backend->refcache, name))) { + + git_oid_cpy(&ref->oid, &oid); + ref->flags = PACKREF_WAS_LOOSE; + } + + git_sortedcache_wunlock(backend->refcache); + +done: + git_buf_free(&ref_file); + return error; +} static int _dirent_loose_load(void *data, git_buf *full_path) { refdb_fs_backend *backend = (refdb_fs_backend *)data; - void *old_ref = NULL; - struct packref *ref; const char *file_path; - int err; - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_load, backend); + if (git__suffixcmp(full_path->ptr, ".lock") == 0) + return 0; + + if (git_path_isdir(full_path->ptr)) + return git_path_direach( + full_path, backend->direach_flags, _dirent_loose_load, backend); file_path = full_path->ptr + strlen(backend->path); - if (loose_lookup_to_packfile(&ref, backend, file_path) < 0) - return -1; - - git_strmap_insert2( - backend->refcache.packfile, ref->name, ref, old_ref, err); - if (err < 0) { - git__free(ref); - return -1; - } - - git__free(old_ref); - return 0; + return loose_lookup_to_packfile(backend, file_path); } /* @@ -374,11 +289,8 @@ static int _dirent_loose_load(void *data, git_buf *full_path) */ static int packed_loadloose(refdb_fs_backend *backend) { + int error; git_buf refs_path = GIT_BUF_INIT; - int result; - - /* the packfile must have been previously loaded! */ - assert(backend->refcache.packfile); if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) return -1; @@ -388,10 +300,12 @@ static int packed_loadloose(refdb_fs_backend *backend) * This will overwrite any old packed entries with their * updated loose versions */ - result = git_path_direach(&refs_path, _dirent_loose_load, backend); + error = git_path_direach( + &refs_path, backend->direach_flags, _dirent_loose_load, backend); + git_buf_free(&refs_path); - return result; + return (error == GIT_EUSER) ? -1 : error; } static int refdb_fs_backend__exists( @@ -399,23 +313,17 @@ static int refdb_fs_backend__exists( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_buf ref_path = GIT_BUF_INIT; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0 || + git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) return -1; - if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) - return -1; - - if (git_path_isfile(ref_path.ptr) == true || - git_strmap_exists(backend->refcache.packfile, ref_path.ptr)) - *exists = 1; - else - *exists = 0; + *exists = git_path_isfile(ref_path.ptr) || + (git_sortedcache_lookup(backend->refcache, ref_name) != NULL); git_buf_free(&ref_path); return 0; @@ -447,64 +355,39 @@ static int loose_lookup( refdb_fs_backend *backend, const char *ref_name) { - const char *target; - git_oid oid; git_buf ref_file = GIT_BUF_INIT; int error = 0; - error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); + if (out) + *out = NULL; - if (error < 0) - goto done; + if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0) + /* cannot read loose ref file - gah */; + else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { + const char *target; - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { git_buf_rtrim(&ref_file); - if ((target = loose_parse_symbolic(&ref_file)) == NULL) { + if (!(target = loose_parse_symbolic(&ref_file))) error = -1; - goto done; - } - - *out = git_reference__alloc_symbolic(ref_name, target); + else if (out != NULL) + *out = git_reference__alloc_symbolic(ref_name, target); } else { - if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0) - goto done; + git_oid oid; - *out = git_reference__alloc(ref_name, &oid, NULL); + if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && + out != NULL) + *out = git_reference__alloc(ref_name, &oid, NULL); } - if (*out == NULL) - error = -1; - -done: git_buf_free(&ref_file); return error; } -static int packed_map_entry( - struct packref **entry, - khiter_t *pos, - refdb_fs_backend *backend, - const char *ref_name) +static int ref_error_notfound(const char *name) { - git_strmap *packfile_refs; - - if (packed_load(backend) < 0) - return -1; - - /* Look up on the packfile */ - packfile_refs = backend->refcache.packfile; - - *pos = git_strmap_lookup_index(packfile_refs, ref_name); - - if (!git_strmap_valid_index(packfile_refs, *pos)) { - giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name); - return GIT_ENOTFOUND; - } - - *entry = git_strmap_value_at(packfile_refs, *pos); - - return 0; + giterr_set(GITERR_REFERENCE, "Reference '%s' not found", name); + return GIT_ENOTFOUND; } static int packed_lookup( @@ -512,18 +395,27 @@ static int packed_lookup( refdb_fs_backend *backend, const char *ref_name) { - struct packref *entry; - khiter_t pos; int error = 0; + struct packref *entry; - if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) - return error; - - if ((*out = git_reference__alloc(ref_name, - &entry->oid, &entry->peel)) == NULL) + if (packed_reload(backend) < 0) return -1; - return 0; + if (git_sortedcache_rlock(backend->refcache) < 0) + return -1; + + entry = git_sortedcache_lookup(backend->refcache, ref_name); + if (!entry) { + error = ref_error_notfound(ref_name); + } else { + *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); + if (!*out) + error = -1; + } + + git_sortedcache_runlock(backend->refcache); + + return error; } static int refdb_fs_backend__lookup( @@ -531,73 +423,68 @@ static int refdb_fs_backend__lookup( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; - int result; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + int error; - assert(_backend); + assert(backend); - backend = (refdb_fs_backend *)_backend; - - if ((result = loose_lookup(out, backend, ref_name)) == 0) + if (!(error = loose_lookup(out, backend, ref_name))) return 0; /* only try to lookup this reference on the packfile if it * wasn't found on the loose refs; not if there was a critical error */ - if (result == GIT_ENOTFOUND) { + if (error == GIT_ENOTFOUND) { giterr_clear(); - result = packed_lookup(out, backend, ref_name); + error = packed_lookup(out, backend, ref_name); } - return result; + return error; } typedef struct { git_reference_iterator parent; char *glob; + + git_pool pool; git_vector loose; - unsigned int loose_pos; - khiter_t packed_pos; + + size_t loose_pos; + size_t packed_pos; } refdb_fs_iter; static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) { refdb_fs_iter *iter = (refdb_fs_iter *) _iter; - char *loose_path; - size_t i; - - git_vector_foreach(&iter->loose, i, loose_path) { - git__free(loose_path); - } git_vector_free(&iter->loose); - - git__free(iter->glob); + git_pool_clear(&iter->pool); git__free(iter); } static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) { - git_strmap *packfile = backend->refcache.packfile; + int error = 0; git_buf path = GIT_BUF_INIT; - git_iterator *fsit; + git_iterator *fsit = NULL; const git_index_entry *entry = NULL; if (!backend->path) /* do nothing if no path for loose refs */ return 0; - if (git_buf_printf(&path, "%s/refs", backend->path) < 0) - return -1; + 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) { + git_buf_free(&path); + return error; + } - if (git_iterator_for_filesystem(&fsit, git_buf_cstr(&path), 0, NULL, NULL) < 0) - return -1; + error = git_buf_sets(&path, GIT_REFS_DIR); - git_vector_init(&iter->loose, 8, NULL); - git_buf_sets(&path, GIT_REFS_DIR); - - while (!git_iterator_advance(&entry, fsit)) { + while (!error && !git_iterator_advance(&entry, fsit)) { const char *ref_name; - khiter_t pos; + struct packref *ref; + char *ref_dup; git_buf_truncate(&path, strlen(GIT_REFS_DIR)); git_buf_puts(&path, entry->path); @@ -607,27 +494,32 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) continue; - pos = git_strmap_lookup_index(packfile, ref_name); - if (git_strmap_valid_index(packfile, pos)) { - struct packref *ref = git_strmap_value_at(packfile, pos); + git_sortedcache_rlock(backend->refcache); + ref = git_sortedcache_lookup(backend->refcache, ref_name); + if (ref) ref->flags |= PACKREF_SHADOWED; - } + git_sortedcache_runlock(backend->refcache); - git_vector_insert(&iter->loose, git__strdup(ref_name)); + ref_dup = git_pool_strdup(&iter->pool, ref_name); + if (!ref_dup) + error = -1; + else + error = git_vector_insert(&iter->loose, ref_dup); } git_iterator_free(fsit); git_buf_free(&path); - return 0; + return error; } static int refdb_fs_backend__iterator_next( git_reference **out, git_reference_iterator *_iter) { + int error = GIT_ITEROVER; refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; - git_strmap *packfile = backend->refcache.packfile; + struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); @@ -638,99 +530,102 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - while (iter->packed_pos < kh_end(packfile)) { - struct packref *ref = NULL; + git_sortedcache_rlock(backend->refcache); - while (!kh_exist(packfile, iter->packed_pos)) { - iter->packed_pos++; - if (iter->packed_pos == kh_end(packfile)) - return GIT_ITEROVER; - } - - ref = kh_val(packfile, iter->packed_pos); - iter->packed_pos++; + while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { + ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) continue; *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); - if (*out == NULL) - return -1; - - return 0; + error = (*out != NULL) ? 0 : -1; + break; } - return GIT_ITEROVER; + git_sortedcache_runlock(backend->refcache); + return error; } static int refdb_fs_backend__iterator_next_name( const char **out, git_reference_iterator *_iter) { + int error = GIT_ITEROVER; refdb_fs_iter *iter = (refdb_fs_iter *)_iter; refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; - git_strmap *packfile = backend->refcache.packfile; + struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - if (git_strmap_exists(packfile, path)) - continue; - - *out = path; - return 0; - } - - while (iter->packed_pos < kh_end(packfile)) { - while (!kh_exist(packfile, iter->packed_pos)) { - iter->packed_pos++; - if (iter->packed_pos == kh_end(packfile)) - return GIT_ITEROVER; + if (loose_lookup(NULL, backend, path) == 0) { + *out = path; + return 0; } - *out = kh_key(packfile, iter->packed_pos); - iter->packed_pos++; - - if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0) - continue; - - return 0; + giterr_clear(); } - return GIT_ITEROVER; + git_sortedcache_rlock(backend->refcache); + + while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { + ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; + + if (ref->flags & PACKREF_SHADOWED) + continue; + if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) + continue; + + *out = ref->name; + error = 0; + break; + } + + git_sortedcache_runlock(backend->refcache); + return error; } static int refdb_fs_backend__iterator( git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) { refdb_fs_iter *iter; - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0) return -1; iter = git__calloc(1, sizeof(refdb_fs_iter)); GITERR_CHECK_ALLOC(iter); - if (glob != NULL) - iter->glob = git__strdup(glob); + if (git_pool_init(&iter->pool, 1, 0) < 0 || + git_vector_init(&iter->loose, 8, NULL) < 0) + goto fail; + + if (glob != NULL && + (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) + goto fail; iter->parent.next = refdb_fs_backend__iterator_next; iter->parent.next_name = refdb_fs_backend__iterator_next_name; iter->parent.free = refdb_fs_backend__iterator_free; - if (iter_load_loose_paths(backend, iter) < 0) { - refdb_fs_backend__iterator_free((git_reference_iterator *)iter); - return -1; - } + if (iter_load_loose_paths(backend, iter) < 0) + goto fail; *out = (git_reference_iterator *)iter; return 0; + +fail: + refdb_fs_backend__iterator_free((git_reference_iterator *)iter); + return -1; } static bool ref_is_available( @@ -756,33 +651,40 @@ static int reference_path_available( const char* old_ref, int force) { - struct packref *this_ref; + size_t i; - if (packed_load(backend) < 0) + if (packed_reload(backend) < 0) return -1; if (!force) { int exists; - if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0) + if (refdb_fs_backend__exists( + &exists, (git_refdb_backend *)backend, new_ref) < 0) return -1; if (exists) { giterr_set(GITERR_REFERENCE, "Failed to write reference '%s': a reference with " - " that name already exists.", new_ref); + "that name already exists.", new_ref); return GIT_EEXISTS; } } - git_strmap_foreach_value(backend->refcache.packfile, this_ref, { - if (!ref_is_available(old_ref, new_ref, this_ref->name)) { + git_sortedcache_rlock(backend->refcache); + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + + if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { + git_sortedcache_runlock(backend->refcache); giterr_set(GITERR_REFERENCE, - "The path to reference '%s' collides with an existing one", new_ref); + "Path to reference '%s' collides with existing one", new_ref); return -1; } - }); - + } + + git_sortedcache_runlock(backend->refcache); return 0; } @@ -800,7 +702,7 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref) if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) return -1; - if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { git_buf_free(&ref_path); return -1; } @@ -809,27 +711,16 @@ static int loose_write(refdb_fs_backend *backend, const git_reference *ref) if (ref->type == GIT_REF_OID) { char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->target.oid); - oid[GIT_OID_HEXSZ] = '\0'; + git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); git_filebuf_printf(&file, "%s\n", oid); - } else if (ref->type == GIT_REF_SYMBOLIC) { git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); } else { assert(0); /* don't let this happen */ } - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); -} - -static int packed_sort(const void *a, const void *b) -{ - const struct packref *ref_a = (const struct packref *)a; - const struct packref *ref_b = (const struct packref *)b; - - return strcmp(ref_a->name, ref_b->name); + return git_filebuf_commit(&file); } /* @@ -884,9 +775,7 @@ static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) static int packed_write_ref(struct packref *ref, git_filebuf *file) { char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->oid); - oid[GIT_OID_HEXSZ] = 0; + git_oid_nfmt(oid, sizeof(oid), &ref->oid); /* * For references that peel to an object in the repo, we must @@ -900,8 +789,7 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) */ if (ref->flags & PACKREF_HAS_PEEL) { char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel); - peel[GIT_OID_HEXSZ] = 0; + git_oid_nfmt(peel, sizeof(peel), &ref->peel); if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) return -1; @@ -924,31 +812,30 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) * is well-written, because we are destructing references * here otherwise. */ -static int packed_remove_loose( - refdb_fs_backend *backend, - git_vector *packing_list) +static int packed_remove_loose(refdb_fs_backend *backend) { size_t i; git_buf full_path = GIT_BUF_INIT; int failed = 0; - for (i = 0; i < packing_list->length; ++i) { - struct packref *ref = git_vector_get(packing_list, i); + /* backend->refcache is already locked when this is called */ - if ((ref->flags & PACKREF_WAS_LOOSE) == 0) + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + + if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) continue; if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) return -1; /* critical; do not try to recover on oom */ - if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { + if (git_path_exists(full_path.ptr) && p_unlink(full_path.ptr) < 0) { if (failed) continue; giterr_set(GITERR_REFERENCE, "Failed to remove loose reference '%s' after packing: %s", full_path.ptr, strerror(errno)); - failed = 1; } @@ -969,85 +856,53 @@ static int packed_remove_loose( */ static int packed_write(refdb_fs_backend *backend) { + git_sortedcache *refcache = backend->refcache; git_filebuf pack_file = GIT_FILEBUF_INIT; size_t i; - git_buf pack_file_path = GIT_BUF_INIT; - git_vector packing_list; - unsigned int total_refs; - assert(backend && backend->refcache.packfile); - - total_refs = - (unsigned int)git_strmap_num_entries(backend->refcache.packfile); - - if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) + /* lock the cache to updates while we do this */ + if (git_sortedcache_wlock(refcache) < 0) return -1; - /* Load all the packfile into a vector */ - { - struct packref *reference; - - /* cannot fail: vector already has the right size */ - git_strmap_foreach_value(backend->refcache.packfile, reference, { - git_vector_insert(&packing_list, reference); - }); - } - - /* sort the vector so the entries appear sorted on the packfile */ - git_vector_sort(&packing_list); - - /* Now we can open the file! */ - if (git_buf_joinpath(&pack_file_path, - backend->path, GIT_PACKEDREFS_FILE) < 0) - goto cleanup_memory; - - if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) - goto cleanup_packfile; + /* Open the file! */ + if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE) < 0) + goto fail; /* Packfiles have a header... apparently * This is in fact not required, but we might as well print it * just for kicks */ if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) - goto cleanup_packfile; + goto fail; - for (i = 0; i < packing_list.length; ++i) { - struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); + for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { + struct packref *ref = git_sortedcache_entry(refcache, i); if (packed_find_peel(backend, ref) < 0) - goto cleanup_packfile; + goto fail; if (packed_write_ref(ref, &pack_file) < 0) - goto cleanup_packfile; + goto fail; } /* if we've written all the references properly, we can commit * the packfile to make the changes effective */ - if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) - goto cleanup_memory; + if (git_filebuf_commit(&pack_file) < 0) + goto fail; /* when and only when the packfile has been properly written, * we can go ahead and remove the loose refs */ - if (packed_remove_loose(backend, &packing_list) < 0) - goto cleanup_memory; + if (packed_remove_loose(backend) < 0) + goto fail; - { - struct stat st; - if (p_stat(pack_file_path.ptr, &st) == 0) - backend->refcache.packfile_time = st.st_mtime; - } - - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); + git_sortedcache_updated(refcache); + git_sortedcache_wunlock(refcache); /* we're good now */ return 0; -cleanup_packfile: +fail: git_filebuf_cleanup(&pack_file); - -cleanup_memory: - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); + git_sortedcache_wunlock(refcache); return -1; } @@ -1057,11 +912,10 @@ static int refdb_fs_backend__write( const git_reference *ref, int force) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; int error; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); error = reference_path_available(backend, ref->name, NULL, force); if (error < 0) @@ -1074,17 +928,13 @@ static int refdb_fs_backend__delete( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_buf loose_path = GIT_BUF_INIT; - struct packref *pack_ref; - khiter_t pack_ref_pos; + size_t pack_pos; int error = 0; bool loose_deleted = 0; - assert(_backend); - assert(ref_name); - - backend = (refdb_fs_backend *)_backend; + assert(backend && ref_name); /* If a loose reference exists, remove it from the filesystem */ if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0) @@ -1100,19 +950,23 @@ static int refdb_fs_backend__delete( if (error != 0) return error; + if (packed_reload(backend) < 0) + return -1; + /* If a packed reference exists, remove it from the packfile and repack */ - error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name); + if (git_sortedcache_wlock(backend->refcache) < 0) + return -1; + + if (!(error = git_sortedcache_lookup_index( + &pack_pos, backend->refcache, ref_name))) + error = git_sortedcache_remove(backend->refcache, pack_pos); + + git_sortedcache_wunlock(backend->refcache); if (error == GIT_ENOTFOUND) - return loose_deleted ? 0 : GIT_ENOTFOUND; + return loose_deleted ? 0 : ref_error_notfound(ref_name); - if (error == 0) { - git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos); - git__free(pack_ref); - error = packed_write(backend); - } - - return error; + return packed_write(backend); } static int refdb_fs_backend__rename( @@ -1122,53 +976,44 @@ static int refdb_fs_backend__rename( const char *new_name, int force) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_reference *old, *new; int error; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - error = reference_path_available(backend, new_name, old_name, force); - if (error < 0) + if ((error = reference_path_available( + backend, new_name, old_name, force)) < 0 || + (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) return error; - error = refdb_fs_backend__lookup(&old, _backend, old_name); - if (error < 0) - return error; - - error = refdb_fs_backend__delete(_backend, old_name); - if (error < 0) { + if ((error = refdb_fs_backend__delete(_backend, old_name)) < 0) { git_reference_free(old); return error; } - new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1); - memcpy(new->name, new_name, strlen(new_name) + 1); + new = git_reference__set_name(old, new_name); + if (!new) { + git_reference_free(old); + return -1; + } - error = loose_write(backend, new); - if (error < 0) { + if ((error = loose_write(backend, new)) < 0 || out == NULL) { git_reference_free(new); return error; } - if (out) { - *out = new; - } else { - git_reference_free(new); - } - + *out = new; return 0; } static int refdb_fs_backend__compress(git_refdb_backend *_backend) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - if (packed_load(backend) < 0 || /* load the existing packfile */ + if (packed_reload(backend) < 0 || /* load the existing packfile */ packed_loadloose(backend) < 0 || /* add all the loose refs */ packed_write(backend) < 0) /* write back to disk */ return -1; @@ -1176,29 +1021,13 @@ static int refdb_fs_backend__compress(git_refdb_backend *_backend) return 0; } -static void refcache_free(git_refcache *refs) -{ - assert(refs); - - if (refs->packfile) { - struct packref *reference; - - git_strmap_foreach_value(refs->packfile, reference, { - git__free(reference); - }); - - git_strmap_free(refs->packfile); - } -} - static void refdb_fs_backend__free(git_refdb_backend *_backend) { - refdb_fs_backend *backend; + refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - assert(_backend); - backend = (refdb_fs_backend *)_backend; + assert(backend); - refcache_free(&backend->refcache); + git_sortedcache_free(backend->refcache); git__free(backend->path); git__free(backend); } @@ -1222,7 +1051,7 @@ static int setup_namespace(git_buf *path, git_repository *repo) if (parts == NULL) return -1; - /** + /* * From `man gitnamespaces`: * namespaces which include a / will expand to a hierarchy * of namespaces; for example, GIT_NAMESPACE=foo/bar will store @@ -1239,15 +1068,355 @@ static int setup_namespace(git_buf *path, git_repository *repo) if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) return -1; - /* Return the root of the namespaced path, i.e. without the trailing '/refs' */ + /* Return root of the namespaced path, i.e. without the trailing '/refs' */ git_buf_rtruncate_at_char(path, '/'); return 0; } +static int reflog_alloc(git_reflog **reflog, const char *name) +{ + git_reflog *log; + + *reflog = NULL; + + log = git__calloc(1, sizeof(git_reflog)); + GITERR_CHECK_ALLOC(log); + + log->ref_name = git__strdup(name); + GITERR_CHECK_ALLOC(log->ref_name); + + if (git_vector_init(&log->entries, 0, NULL) < 0) { + git__free(log->ref_name); + git__free(log); + return -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + const char *ptr; + git_reflog_entry *entry; + +#define seek_forward(_increase) do { \ + if (_increase >= buf_size) { \ + giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ + goto fail; \ + } \ + buf += _increase; \ + buf_size -= _increase; \ + } while (0) + + while (buf_size > GIT_REFLOG_SIZE_MIN) { + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->committer = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(entry->committer); + + if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + ptr = buf; + + /* Seek forward to the end of the signature. */ + while (*buf && *buf != '\t' && *buf != '\n') + seek_forward(1); + + if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) + goto fail; + + if (*buf == '\t') { + /* We got a message. Read everything till we reach LF. */ + seek_forward(1); + ptr = buf; + + while (*buf && *buf != '\n') + seek_forward(1); + + entry->msg = git__strndup(ptr, buf - ptr); + GITERR_CHECK_ALLOC(entry->msg); + } else + entry->msg = NULL; + + while (*buf && *buf == '\n' && buf_size > 1) + seek_forward(1); + + if (git_vector_insert(&log->entries, entry) < 0) + goto fail; + } + + return 0; + +#undef seek_forward + +fail: + if (entry) + git_reflog_entry__free(entry); + + return -1; +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT | O_TRUNC, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + +GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name) +{ + return git_buf_join_n(path, '/', 3, repo->path_repository, GIT_REFLOG_DIR, name); +} + +static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + int error = -1; + git_buf log_path = GIT_BUF_INIT; + git_buf log_file = GIT_BUF_INIT; + git_reflog *log = NULL; + git_repository *repo; + refdb_fs_backend *backend; + + assert(out && _backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (reflog_alloc(&log, name) < 0) + return -1; + + if (retrieve_reflog_path(&log_path, repo, name) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; + + *out = log; + goto success; + +cleanup: + git_reflog_free(log); + +success: + git_buf_free(&log_file); + git_buf_free(&log_path); + + return error; +} + +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) +{ + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; + + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_buf_clear(buf); + + git_buf_puts(buf, raw_old); + git_buf_putc(buf, ' '); + git_buf_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_buf_rtrim(buf); + + if (msg) { + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); + } + + git_buf_putc(buf, '\n'); + + return git_buf_oom(buf); +} + +static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + git_repository *repo; + refdb_fs_backend *backend; + git_buf log_path = GIT_BUF_INIT; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(_backend && reflog); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if (retrieve_reflog_path(&log_path, repo, reflog->ref_name) < 0) + return -1; + + if (!git_path_isfile(git_buf_cstr(&log_path))) { + giterr_set(GITERR_INVALID, + "Log file for reference '%s' doesn't exist.", reflog->ref_name); + goto cleanup; + } + + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + int error = 0, fd; + git_buf old_path = GIT_BUF_INIT; + git_buf new_path = GIT_BUF_INIT; + git_buf temp_path = GIT_BUF_INIT; + git_buf normalized = GIT_BUF_INIT; + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && old_name && new_name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + if ((error = git_reference__normalize_name( + &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) + return error; + + if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0) + return -1; + + if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0) + return -1; + + if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0) + return -1; + + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) + return -1; + + if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) { + error = -1; + goto cleanup; + } + + p_close(fd); + + if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + goto cleanup; + } + + if (git_path_isdir(git_buf_cstr(&new_path)) && + (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { + error = -1; + goto cleanup; + } + + if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { + error = -1; + goto cleanup; + } + + if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) { + giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); + error = -1; + } + +cleanup: + git_buf_free(&temp_path); + git_buf_free(&old_path); + git_buf_free(&new_path); + git_buf_free(&normalized); + + return error; +} + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) +{ + int error; + git_buf path = GIT_BUF_INIT; + + git_repository *repo; + refdb_fs_backend *backend; + + assert(_backend && name); + + backend = (refdb_fs_backend *) _backend; + repo = backend->repo; + + error = retrieve_reflog_path(&path, repo, name); + + if (!error && git_path_exists(path.ptr)) + error = p_unlink(path.ptr); + + git_buf_free(&path); + + return error; + +} + int git_refdb_backend_fs( git_refdb_backend **backend_out, git_repository *repository) { + int t = 0; git_buf path = GIT_BUF_INIT; refdb_fs_backend *backend; @@ -1256,22 +1425,47 @@ int git_refdb_backend_fs( backend->repo = repository; - if (setup_namespace(&path, repository) < 0) { - git__free(backend); - return -1; - } + if (setup_namespace(&path, repository) < 0) + goto fail; backend->path = git_buf_detach(&path); + if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 || + git_sortedcache_new( + &backend->refcache, offsetof(struct packref, name), + NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0) + goto fail; + + git_buf_free(&path); + + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { + backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; + backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE; + } + if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) { + backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE; + } + backend->parent.exists = &refdb_fs_backend__exists; backend->parent.lookup = &refdb_fs_backend__lookup; backend->parent.iterator = &refdb_fs_backend__iterator; backend->parent.write = &refdb_fs_backend__write; - backend->parent.delete = &refdb_fs_backend__delete; + backend->parent.del = &refdb_fs_backend__delete; backend->parent.rename = &refdb_fs_backend__rename; backend->parent.compress = &refdb_fs_backend__compress; backend->parent.free = &refdb_fs_backend__free; + backend->parent.reflog_read = &refdb_reflog_fs__read; + backend->parent.reflog_write = &refdb_reflog_fs__write; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; *backend_out = (git_refdb_backend *)backend; return 0; + +fail: + git_buf_free(&path); + git__free(backend->path); + git__free(backend); + return -1; } diff --git a/src/reflog.c b/src/reflog.c index 4cc20d2c7..cebb87d86 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -9,82 +9,16 @@ #include "repository.h" #include "filebuf.h" #include "signature.h" +#include "refdb.h" -static int reflog_init(git_reflog **reflog, const git_reference *ref) +#include + +git_reflog_entry *git_reflog_entry__alloc(void) { - git_reflog *log; - - *reflog = NULL; - - log = git__calloc(1, sizeof(git_reflog)); - GITERR_CHECK_ALLOC(log); - - log->ref_name = git__strdup(ref->name); - GITERR_CHECK_ALLOC(log->ref_name); - - if (git_vector_init(&log->entries, 0, NULL) < 0) { - git__free(log->ref_name); - git__free(log); - return -1; - } - - log->owner = git_reference_owner(ref); - *reflog = log; - - return 0; + return git__calloc(1, sizeof(git_reflog_entry)); } -static int serialize_reflog_entry( - git_buf *buf, - const git_oid *oid_old, - const git_oid *oid_new, - const git_signature *committer, - const char *msg) -{ - char raw_old[GIT_OID_HEXSZ+1]; - char raw_new[GIT_OID_HEXSZ+1]; - - git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); - git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - - git_buf_clear(buf); - - git_buf_puts(buf, raw_old); - git_buf_putc(buf, ' '); - git_buf_puts(buf, raw_new); - - git_signature__writebuf(buf, " ", committer); - - /* drop trailing LF */ - git_buf_rtrim(buf); - - if (msg) { - git_buf_putc(buf, '\t'); - git_buf_puts(buf, msg); - } - - git_buf_putc(buf, '\n'); - - return git_buf_oom(buf); -} - -static int reflog_entry_new(git_reflog_entry **entry) -{ - git_reflog_entry *e; - - assert(entry); - - e = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(e); - - memset(e, 0, sizeof(git_reflog_entry)); - - *entry = e; - - return 0; -} - -static void reflog_entry_free(git_reflog_entry *entry) +void git_reflog_entry__free(git_reflog_entry *entry) { git_signature_free(entry->committer); @@ -92,75 +26,6 @@ static void reflog_entry_free(git_reflog_entry *entry) git__free(entry); } -static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) -{ - const char *ptr; - git_reflog_entry *entry; - -#define seek_forward(_increase) do { \ - if (_increase >= buf_size) { \ - giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ - goto fail; \ - } \ - buf += _increase; \ - buf_size -= _increase; \ - } while (0) - - while (buf_size > GIT_REFLOG_SIZE_MIN) { - if (reflog_entry_new(&entry) < 0) - return -1; - - entry->committer = git__malloc(sizeof(git_signature)); - GITERR_CHECK_ALLOC(entry->committer); - - if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); - - if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); - - ptr = buf; - - /* Seek forward to the end of the signature. */ - while (*buf && *buf != '\t' && *buf != '\n') - seek_forward(1); - - if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) - goto fail; - - if (*buf == '\t') { - /* We got a message. Read everything till we reach LF. */ - seek_forward(1); - ptr = buf; - - while (*buf && *buf != '\n') - seek_forward(1); - - entry->msg = git__strndup(ptr, buf - ptr); - GITERR_CHECK_ALLOC(entry->msg); - } else - entry->msg = NULL; - - while (*buf && *buf == '\n' && buf_size > 1) - seek_forward(1); - - if (git_vector_insert(&log->entries, entry) < 0) - goto fail; - } - - return 0; - -#undef seek_forward - -fail: - if (entry) - reflog_entry_free(entry); - - return -1; -} - void git_reflog_free(git_reflog *reflog) { size_t i; @@ -169,10 +34,13 @@ void git_reflog_free(git_reflog *reflog) if (reflog == NULL) return; + if (reflog->db) + GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); + for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - reflog_entry_free(entry); + git_reflog_entry__free(entry); } git_vector_free(&reflog->entries); @@ -180,115 +48,30 @@ void git_reflog_free(git_reflog *reflog) git__free(reflog); } -static int retrieve_reflog_path(git_buf *path, const git_reference *ref) +int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) { - return git_buf_join_n(path, '/', 3, - git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); -} + git_refdb *refdb; + int error; -static int create_new_reflog_file(const char *filepath) -{ - int fd, error; + assert(reflog && repo && name); - if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; - if ((fd = p_open(filepath, - O_WRONLY | O_CREAT | O_TRUNC, - GIT_REFLOG_FILE_MODE)) < 0) - return -1; - - return p_close(fd); -} - -int git_reflog_read(git_reflog **reflog, const git_reference *ref) -{ - int error = -1; - git_buf log_path = GIT_BUF_INIT; - git_buf log_file = GIT_BUF_INIT; - git_reflog *log = NULL; - - assert(reflog && ref); - - *reflog = NULL; - - if (reflog_init(&log, ref) < 0) - return -1; - - if (retrieve_reflog_path(&log_path, ref) < 0) - goto cleanup; - - error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if ((error == GIT_ENOTFOUND) && - ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) - goto cleanup; - - if ((error = reflog_parse(log, - git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) - goto cleanup; - - *reflog = log; - goto success; - -cleanup: - git_reflog_free(log); - -success: - git_buf_free(&log_file); - git_buf_free(&log_path); - - return error; + return git_refdb_reflog_read(reflog, refdb, name); } int git_reflog_write(git_reflog *reflog) { - int error = -1; - unsigned int i; - git_reflog_entry *entry; - git_buf log_path = GIT_BUF_INIT; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; + git_refdb *db; - assert(reflog); + assert(reflog && reflog->db); - if (git_buf_join_n(&log_path, '/', 3, - git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) - return -1; - - if (!git_path_isfile(git_buf_cstr(&log_path))) { - giterr_set(GITERR_INVALID, - "Log file for reference '%s' doesn't exist.", reflog->ref_name); - goto cleanup; - } - - if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) - goto cleanup; - - git_vector_foreach(&reflog->entries, i, entry) { - if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) - goto cleanup; - - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - goto success; - -cleanup: - git_filebuf_cleanup(&fbuf); - -success: - git_buf_free(&log); - git_buf_free(&log_path); - return error; + db = reflog->db; + return db->backend->reflog_write(db->backend, reflog); } -int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, - const git_signature *committer, const char *msg) +int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { git_reflog_entry *entry; const git_reflog_entry *previous; @@ -296,8 +79,8 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, assert(reflog && new_oid && committer); - if (reflog_entry_new(&entry) < 0) - return -1; + entry = git__calloc(1, sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(entry); if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; @@ -333,94 +116,30 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, return 0; cleanup: - reflog_entry_free(entry); + git_reflog_entry__free(entry); return -1; } -int git_reflog_rename(git_reference *ref, const char *new_name) +int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) { - int error = 0, fd; - git_buf old_path = GIT_BUF_INIT; - git_buf new_path = GIT_BUF_INIT; - git_buf temp_path = GIT_BUF_INIT; - git_buf normalized = GIT_BUF_INIT; + git_refdb *refdb; + int error; - assert(ref && new_name); - - if ((error = git_reference__normalize_name( - &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) - return error; - - if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return -1; - if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0) - return -1; - - if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0) - return -1; - - /* - * Move the reflog to a temporary place. This two-phase renaming is required - * in order to cope with funny renaming use cases when one tries to move a reference - * to a partially colliding namespace: - * - a/b -> a/b/c - * - a/b/c/d -> a/b/c - */ - if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) - return -1; - - if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) { - error = -1; - goto cleanup; - } - - p_close(fd); - - if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) { - giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); - error = -1; - goto cleanup; - } - - if (git_path_isdir(git_buf_cstr(&new_path)) && - (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { - error = -1; - goto cleanup; - } - - if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { - error = -1; - goto cleanup; - } - - if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) { - giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name); - error = -1; - } - -cleanup: - git_buf_free(&temp_path); - git_buf_free(&old_path); - git_buf_free(&new_path); - git_buf_free(&normalized); - - return error; + return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); } -int git_reflog_delete(git_reference *ref) +int git_reflog_delete(git_repository *repo, const char *name) { + git_refdb *refdb; int error; - git_buf path = GIT_BUF_INIT; - error = retrieve_reflog_path(&path, ref); + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - if (!error && git_path_exists(path.ptr)) - error = p_unlink(path.ptr); - - git_buf_free(&path); - - return error; + return refdb->backend->reflog_delete(refdb->backend, name); } size_t git_reflog_entrycount(git_reflog *reflog) @@ -429,11 +148,6 @@ size_t git_reflog_entrycount(git_reflog *reflog) return reflog->entries.length; } -GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) -{ - return (total - 1) - idx; -} - const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) { assert(reflog); @@ -469,16 +183,11 @@ const char * git_reflog_entry_message(const git_reflog_entry *entry) return entry->msg; } -int git_reflog_drop( - git_reflog *reflog, - size_t idx, - int rewrite_previous_entry) +int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) { size_t entrycount; git_reflog_entry *entry, *previous; - assert(reflog); - entrycount = git_reflog_entrycount(reflog); entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); @@ -488,7 +197,7 @@ int git_reflog_drop( return GIT_ENOTFOUND; } - reflog_entry_free(entry); + git_reflog_entry__free(entry); if (git_vector_remove( &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) @@ -521,3 +230,22 @@ int git_reflog_drop( return 0; } + +int git_reflog_append_to(git_repository *repo, const char *name, const git_oid *id, + const git_signature *committer, const char *msg) +{ + int error; + git_reflog *reflog; + + if ((error = git_reflog_read(&reflog, repo, name)) < 0) + return error; + + if ((error = git_reflog_append(reflog, id, committer, msg)) < 0) + goto cleanup; + + error = git_reflog_write(reflog); + +cleanup: + git_reflog_free(reflog); + return error; +} diff --git a/src/reflog.h b/src/reflog.h index 9444ebd10..2d31ae47d 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -27,9 +27,14 @@ struct git_reflog_entry { }; struct git_reflog { + git_refdb *db; char *ref_name; - git_repository *owner; git_vector entries; }; +GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) +{ + return (total - 1) - idx; +} + #endif /* INCLUDE_reflog_h__ */ diff --git a/src/refs.c b/src/refs.c index c0e460cc3..472a79890 100644 --- a/src/refs.c +++ b/src/refs.c @@ -88,6 +88,17 @@ git_reference *git_reference__alloc( return ref; } +git_reference *git_reference__set_name( + git_reference *ref, const char *name) +{ + size_t namelen = strlen(name); + git_reference *rewrite = + git__realloc(ref, sizeof(git_reference) + namelen + 1); + if (rewrite != NULL) + memcpy(rewrite->name, name, namelen + 1); + return rewrite; +} + void git_reference_free(git_reference *reference) { if (reference == NULL) @@ -127,6 +138,22 @@ int git_reference_name_to_id( return 0; } +static int reference_normalize_for_repo( + char *out, + size_t out_size, + git_repository *repo, + const char *name) +{ + int precompose; + unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL; + + if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) && + precompose) + flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE; + + return git_reference_normalize_name(out, out_size, name, flags); +} + int git_reference_lookup_resolved( git_reference **ref_out, git_repository *repo, @@ -148,13 +175,13 @@ int git_reference_lookup_resolved( else if (max_nesting < 0) max_nesting = DEFAULT_NESTING_LEVEL; - strncpy(scan_name, name, GIT_REFNAME_MAX); scan_type = GIT_REF_SYMBOLIC; - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; + if ((error = reference_normalize_for_repo( + scan_name, sizeof(scan_name), repo, name)) < 0) + return error; - if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; for (nesting = max_nesting; @@ -162,7 +189,7 @@ int git_reference_lookup_resolved( nesting--) { if (nesting != max_nesting) { - strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); + strncpy(scan_name, ref->target.symbolic, sizeof(scan_name)); git_reference_free(ref); } @@ -456,7 +483,7 @@ int git_reference_rename( if (reference_has_log < 0) return reference_has_log; - if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) + if (reference_has_log && (error = git_reflog_rename(git_reference_owner(ref), git_reference_name(ref), new_name)) < 0) return error; return 0; @@ -700,17 +727,21 @@ static bool is_all_caps_and_underscore(const char *name, size_t len) return true; } +/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ int git_reference__normalize_name( git_buf *buf, const char *name, unsigned int flags) { - // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 - char *current; int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; unsigned int process_flags; bool normalize = (buf != NULL); + +#ifdef GIT_USE_ICONV + git_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + assert(name); process_flags = flags; @@ -722,6 +753,16 @@ int git_reference__normalize_name( if (normalize) git_buf_clear(buf); +#ifdef GIT_USE_ICONV + if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) { + size_t namelen = strlen(current); + if ((error = git_path_iconv_init_precompose(&ic)) < 0 || + (error = git_path_iconv(&ic, ¤t, &namelen)) < 0) + goto cleanup; + error = GIT_EINVALIDSPEC; + } +#endif + while (true) { segment_len = ensure_segment_validity(current); if (segment_len < 0) { @@ -798,6 +839,10 @@ cleanup: if (error && normalize) git_buf_free(buf); +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&ic); +#endif + return error; } @@ -952,6 +997,17 @@ int git_reference_is_remote(git_reference *ref) return git_reference__is_remote(ref->name); } +int git_reference__is_tag(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; +} + +int git_reference_is_tag(git_reference *ref) +{ + assert(ref); + return git_reference__is_tag(ref->name); +} + static int peel_error(int error, git_reference *ref, const char* msg) { giterr_set( @@ -961,9 +1017,9 @@ static int peel_error(int error, git_reference *ref, const char* msg) } int git_reference_peel( - git_object **peeled, - git_reference *ref, - git_otype target_type) + git_object **peeled, + git_reference *ref, + git_otype target_type) { git_reference *resolved = NULL; git_object *target = NULL; @@ -1005,24 +1061,19 @@ cleanup: return error; } -int git_reference__is_valid_name( - const char *refname, - unsigned int flags) +int git_reference__is_valid_name(const char *refname, unsigned int flags) { - int error; + if (git_reference__normalize_name(NULL, refname, flags) < 0) { + giterr_clear(); + return false; + } - error = git_reference__normalize_name(NULL, refname, flags) == 0; - giterr_clear(); - - return error; + return true; } -int git_reference_is_valid_name( - const char *refname) +int git_reference_is_valid_name(const char *refname) { - return git_reference__is_valid_name( - refname, - GIT_REF_FORMAT_ALLOW_ONELEVEL); + return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL); } const char *git_reference_shorthand(git_reference *ref) diff --git a/src/refs.h b/src/refs.h index f487ee3fc..80c7703fc 100644 --- a/src/refs.h +++ b/src/refs.h @@ -46,6 +46,8 @@ #define GIT_STASH_FILE "stash" #define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE +#define GIT_REF_FORMAT__PRECOMPOSE_UNICODE (1u << 16) + #define GIT_REFNAME_MAX 1024 struct git_reference { @@ -61,12 +63,15 @@ struct git_reference { char name[0]; }; +git_reference *git_reference__set_name(git_reference *ref, const char *name); + int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid); int git_reference__is_valid_name(const char *refname, unsigned int flags); int git_reference__is_branch(const char *ref_name); int git_reference__is_remote(const char *ref_name); +int git_reference__is_tag(const char *ref_name); /** * Lookup a reference by name and try to resolve to an OID. diff --git a/src/refspec.c b/src/refspec.c index a907df84c..a97340071 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -12,6 +12,7 @@ #include "util.h" #include "posix.h" #include "refs.h" +#include "vector.h" int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) { @@ -225,25 +226,31 @@ int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, co return refspec_transform_internal(out, outlen, spec->dst, spec->src, name); } -static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name) +static int refspec_transform( + git_buf *out, const char *from, const char *to, const char *name) { - if (git_buf_sets(out, to) < 0) + size_t to_len = to ? strlen(to) : 0; + size_t from_len = from ? strlen(from) : 0; + size_t name_len = name ? strlen(name) : 0; + + if (git_buf_set(out, to, to_len) < 0) return -1; - /* - * No '*' at the end means that it's mapped to one specific - * branch, so no actual transformation is needed. - */ - if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') - return 0; + if (to_len > 0) { + /* No '*' at the end of 'to' means that refspec is mapped to one + * specific branch, so no actual transformation is needed. + */ + if (out->ptr[to_len - 1] != '*') + return 0; + git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */ + } - git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ - git_buf_puts(out, name + strlen(from) - 1); + if (from_len > 0) /* ignore trailing '*' from 'from' */ + from_len--; + if (from_len > name_len) + from_len = name_len; - if (git_buf_oom(out)) - return -1; - - return 0; + return git_buf_put(out, name + from_len, name_len - from_len); } int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) @@ -281,3 +288,70 @@ git_direction git_refspec_direction(const git_refspec *spec) return spec->push; } + +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) +{ + git_buf buf = GIT_BUF_INIT; + size_t j, pos; + git_remote_head key; + + const char* formatters[] = { + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + NULL + }; + + git_refspec *cur = git__calloc(1, sizeof(git_refspec)); + GITERR_CHECK_ALLOC(cur); + + cur->force = spec->force; + cur->push = spec->push; + cur->pattern = spec->pattern; + cur->matching = spec->matching; + cur->string = git__strdup(spec->string); + + /* shorthand on the lhs */ + 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; + + key.name = (char *) git_buf_cstr(&buf); + if (!git_vector_search(&pos, refs, &key)) { + /* we found something to match the shorthand, set src to that */ + cur->src = git_buf_detach(&buf); + } + } + } + + /* No shorthands found, copy over the name */ + if (cur->src == NULL && spec->src != NULL) { + cur->src = git__strdup(spec->src); + GITERR_CHECK_ALLOC(cur->src); + } + + if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { + /* if it starts with "remotes" then we just prepend "refs/" */ + if (!git__prefixcmp(spec->dst, "remotes/")) { + git_buf_puts(&buf, GIT_REFS_DIR); + } else { + git_buf_puts(&buf, GIT_REFS_HEADS_DIR); + } + + if (git_buf_puts(&buf, spec->dst) < 0) + return -1; + + cur->dst = git_buf_detach(&buf); + } + + git_buf_free(&buf); + + if (cur->dst == NULL && spec->dst != NULL) { + cur->dst = git__strdup(spec->dst); + GITERR_CHECK_ALLOC(cur->dst); + } + + return git_vector_insert(out, cur); +} diff --git a/src/refspec.h b/src/refspec.h index 44d484c7b..51b7bfee9 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -9,6 +9,7 @@ #include "git2/refspec.h" #include "buffer.h" +#include "vector.h" struct git_refspec { char *string; @@ -17,7 +18,6 @@ struct git_refspec { unsigned int force :1, push : 1, pattern :1, - dwim :1, matching :1; }; @@ -63,4 +63,10 @@ int git_refspec__serialize(git_buf *out, const git_refspec *refspec); */ int git_refspec_is_wildcard(const git_refspec *spec); +/** + * DWIM `spec` with `refs` existing on the remote, append the dwim'ed + * result in `out`. + */ +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs); + #endif diff --git a/src/remote.c b/src/remote.c index 0e8354a11..3d890a5f1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -10,6 +10,7 @@ #include "git2/oid.h" #include "git2/net.h" +#include "common.h" #include "config.h" #include "repository.h" #include "remote.h" @@ -18,7 +19,7 @@ #include "refspec.h" #include "fetchhead.h" -#include +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); static int add_refspec(git_remote *remote, const char *string, bool is_fetch) { @@ -81,6 +82,37 @@ static int ensure_remote_name_is_valid(const char *name) return error; } +static int get_check_cert(int *out, git_repository *repo) +{ + git_config *cfg; + const char *val; + int error = 0; + + assert(out && repo); + + /* By default, we *DO* want to verify the certificate. */ + *out = 1; + + /* Go through the possible sources for SSL verification settings, from + * most specific to least specific. */ + + /* GIT_SSL_NO_VERIFY environment variable */ + if ((val = getenv("GIT_SSL_NO_VERIFY")) != NULL) + return git_config_parse_bool(out, val); + + /* http.sslVerify config setting */ + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if ((error = git_config_get_bool(out, cfg, "http.sslVerify")) == 0) + return 0; + else if (error != GIT_ENOTFOUND) + return error; + + giterr_clear(); + return 0; +} + static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) { git_remote *remote; @@ -94,9 +126,11 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n GITERR_CHECK_ALLOC(remote); remote->repo = repo; - remote->check_cert = 1; remote->update_fetchhead = 1; + if (get_check_cert(&remote->check_cert, repo) < 0) + goto on_error; + if (git_vector_init(&remote->refs, 32, NULL) < 0) goto on_error; @@ -183,6 +217,32 @@ on_error: return -1; } +int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) +{ + git_remote *remote = NULL; + int error; + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if ((error = ensure_remote_doesnot_exist(repo, name)) < 0) + return error; + + if (create_internal(&remote, repo, name, url, fetch) < 0) + goto on_error; + + if (git_remote_save(remote) < 0) + goto on_error; + + *out = remote; + + return 0; + +on_error: + git_remote_free(remote); + return -1; +} + int git_remote_create_inmemory(git_remote **out, git_repository *repo, const char *fetch, const char *url) { int error; @@ -208,7 +268,8 @@ static int refspec_cb(const git_config_entry *entry, void *payload) } static int get_optional_config( - git_config *config, git_buf *buf, git_config_foreach_cb cb, void *payload) + bool *found, git_config *config, git_buf *buf, + git_config_foreach_cb cb, void *payload) { int error = 0; const char *key = git_buf_cstr(buf); @@ -217,10 +278,13 @@ static int get_optional_config( return -1; if (cb != NULL) - error = git_config_get_multivar(config, key, NULL, cb, payload); + error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); else error = git_config_get_string(payload, config, key); + if (found) + *found = !error; + if (error == GIT_ENOTFOUND) { giterr_clear(); error = 0; @@ -240,6 +304,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) int error = 0; git_config *config; struct refspec_cb_data data; + bool optional_setting_found = false, found; assert(out && repo && name); @@ -253,13 +318,16 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); - remote->check_cert = 1; remote->update_fetchhead = 1; remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); + if ((error = get_check_cert(&remote->check_cert, repo)) < 0) + goto cleanup; + if ((git_vector_init(&remote->refs, 32, NULL) < 0) || - (git_vector_init(&remote->refspecs, 2, NULL))) { + (git_vector_init(&remote->refspecs, 2, NULL) < 0) || + (git_vector_init(&remote->active_refspecs, 2, NULL) < 0)) { error = -1; goto cleanup; } @@ -269,27 +337,33 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) goto cleanup; } - if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) goto cleanup; - if (strlen(val) == 0) { - giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name); - error = -1; - goto cleanup; - } + optional_setting_found |= found; remote->repo = repo; - remote->url = git__strdup(val); - GITERR_CHECK_ALLOC(remote->url); + + if (found && strlen(val) > 0) { + remote->url = git__strdup(val); + GITERR_CHECK_ALLOC(remote->url); + } val = NULL; git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.pushurl", name); - if ((error = get_optional_config(config, &buf, NULL, (void *)&val)) < 0) + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) goto cleanup; - if (val) { + optional_setting_found |= found; + + if (!optional_setting_found) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (found && strlen(val) > 0) { remote->pushurl = git__strdup(val); GITERR_CHECK_ALLOC(remote->pushurl); } @@ -299,19 +373,23 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.fetch", name); - if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0) + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) goto cleanup; data.fetch = false; git_buf_clear(&buf); git_buf_printf(&buf, "remote.%s.push", name); - if ((error = get_optional_config(config, &buf, refspec_cb, &data)) < 0) + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) goto cleanup; if (download_tags_value(remote, config) < 0) goto cleanup; + /* Move the data over to where the matching functions can find them */ + if (dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs) < 0) + goto cleanup; + *out = remote; cleanup: @@ -326,20 +404,22 @@ cleanup: static int update_config_refspec(const git_remote *remote, git_config *config, int direction) { git_buf name = GIT_BUF_INIT; - int push; + unsigned int push; const char *dir; size_t i; int error = 0; + const char *cname; push = direction == GIT_DIRECTION_PUSH; dir = push ? "push" : "fetch"; if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0) return -1; + cname = git_buf_cstr(&name); /* Clear out the existing config */ while (!error) - error = git_config_delete_entry(config, git_buf_cstr(&name)); + error = git_config_delete_multivar(config, cname, ".*"); if (error != GIT_ENOTFOUND) return error; @@ -350,8 +430,11 @@ static int update_config_refspec(const git_remote *remote, git_config *config, i if (spec->push != push) continue; + // "$^" is a unmatcheable regexp: it will not match anything at all, so + // all values will be considered new and we will not replace any + // present value. if ((error = git_config_set_multivar( - config, git_buf_cstr(&name), "", spec->string)) < 0) { + config, cname, "$^", spec->string)) < 0) { goto cleanup; } } @@ -467,6 +550,12 @@ const char *git_remote_name(const git_remote *remote) return remote->name; } +git_repository *git_remote_owner(const git_remote *remote) +{ + assert(remote); + return remote->repo; +} + const char *git_remote_url(const git_remote *remote) { assert(remote); @@ -509,6 +598,8 @@ const char* git_remote__urlfordirection(git_remote *remote, int direction) { assert(remote); + assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + if (direction == GIT_DIRECTION_FETCH) { return remote->url; } @@ -525,28 +616,32 @@ int git_remote_connect(git_remote *remote, git_direction direction) git_transport *t; const char *url; int flags = GIT_TRANSPORTFLAGS_NONE; + int error; assert(remote); t = remote->transport; url = git_remote__urlfordirection(remote, direction); - if (url == NULL ) + if (url == NULL) { + giterr_set(GITERR_INVALID, + "Malformed remote '%s' - missing URL", remote->name); return -1; + } /* A transport could have been supplied in advance with * git_remote_set_transport */ - if (!t && git_transport_new(&t, remote, url) < 0) - return -1; + if (!t && (error = git_transport_new(&t, remote, url)) < 0) + return error; if (t->set_callbacks && - t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0) + (error = t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload)) < 0) goto on_error; if (!remote->check_cert) flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - if (t->connect(t, url, remote->cred_acquire_cb, remote->cred_acquire_payload, direction, flags) < 0) + if ((error = t->connect(t, url, remote->callbacks.credentials, remote->callbacks.payload, direction, flags)) < 0) goto on_error; remote->transport = t; @@ -559,20 +654,21 @@ on_error: if (t == remote->transport) remote->transport = NULL; - return -1; + return error; } -int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) +int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) { assert(remote); - return remote->transport->ls(remote->transport, list_cb, payload); + return remote->transport->ls(out, size, remote->transport); } int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url) { git_config *cfg; const char *val; + int error; assert(remote); @@ -581,8 +677,8 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur *proxy_url = NULL; - if (git_repository_config__weakptr(&cfg, remote->repo) < 0) - return -1; + if ((error = git_repository_config__weakptr(&cfg, remote->repo)) < 0) + return error; /* Go through the possible sources for proxy configuration, from most specific * to least specific. */ @@ -591,28 +687,33 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur if (remote->name && 0 != *(remote->name)) { git_buf buf = GIT_BUF_INIT; - if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0) - return -1; + if ((error = git_buf_printf(&buf, "remote.%s.proxy", remote->name)) < 0) + return error; - if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) && + if ((error = git_config_get_string(&val, cfg, git_buf_cstr(&buf))) == 0 && val && ('\0' != *val)) { git_buf_free(&buf); *proxy_url = git__strdup(val); GITERR_CHECK_ALLOC(*proxy_url); return 0; - } + } else if (error != GIT_ENOTFOUND) + return error; + giterr_clear(); git_buf_free(&buf); } /* http.proxy config setting */ - if (!git_config_get_string(&val, cfg, "http.proxy") && + if ((error = git_config_get_string(&val, cfg, "http.proxy")) == 0 && val && ('\0' != *val)) { *proxy_url = git__strdup(val); GITERR_CHECK_ALLOC(*proxy_url); return 0; - } + } else if (error != GIT_ENOTFOUND) + return error; + + giterr_clear(); /* HTTP_PROXY / HTTPS_PROXY environment variables */ val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY"); @@ -626,69 +727,33 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur return 0; } -static int store_refs(git_remote_head *head, void *payload) +/* DWIM `refspecs` based on `refs` and append the output to `out` */ +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs) { - git_vector *refs = (git_vector *)payload; - - return git_vector_insert(refs, head); -} - -static int dwim_refspecs(git_vector *refspecs, git_vector *refs) -{ - git_buf buf = GIT_BUF_INIT; + size_t i; git_refspec *spec; - size_t i, j, pos; - git_remote_head key; - - const char* formatters[] = { - GIT_REFS_DIR "%s", - GIT_REFS_TAGS_DIR "%s", - GIT_REFS_HEADS_DIR "%s", - NULL - }; git_vector_foreach(refspecs, i, spec) { - if (spec->dwim) - continue; - - /* shorthand on the lhs */ - 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; - - key.name = (char *) git_buf_cstr(&buf); - if (!git_vector_search(&pos, refs, &key)) { - /* we found something to match the shorthand, set src to that */ - git__free(spec->src); - spec->src = git_buf_detach(&buf); - } - } - } - - if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { - /* if it starts with "remotes" then we just prepend "refs/" */ - if (!git__prefixcmp(spec->dst, "remotes/")) { - git_buf_puts(&buf, GIT_REFS_DIR); - } else { - git_buf_puts(&buf, GIT_REFS_HEADS_DIR); - } - - if (git_buf_puts(&buf, spec->dst) < 0) - return -1; - - git__free(spec->dst); - spec->dst = git_buf_detach(&buf); - } - - spec->dwim = 1; + if (git_refspec__dwim_one(out, spec, refs) < 0) + return -1; } - git_buf_free(&buf); return 0; } +static void free_refspecs(git_vector *vec) +{ + size_t i; + git_refspec *spec; + + git_vector_foreach(vec, i, spec) { + git_refspec__free(spec); + git__free(spec); + } + + git_vector_clear(vec); +} + static int remote_head_cmp(const void *_a, const void *_b) { const git_remote_head *a = (git_remote_head *) _a; @@ -697,32 +762,65 @@ static int remote_head_cmp(const void *_a, const void *_b) return git__strcmp_cb(a->name, b->name); } -int git_remote_download( - git_remote *remote, - git_transfer_progress_callback progress_cb, - void *progress_payload) +static int ls_to_vector(git_vector *out, git_remote *remote) +{ + git_remote_head **heads; + size_t heads_len, i; + + if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) + return -1; + + if (git_vector_init(out, heads_len, remote_head_cmp) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(out, heads[i]) < 0) + return -1; + } + + return 0; +} + +int git_remote_download(git_remote *remote) { int error; git_vector refs; assert(remote); - if (git_vector_init(&refs, 16, remote_head_cmp) < 0) + if (ls_to_vector(&refs, remote) < 0) return -1; - if (git_remote_ls(remote, store_refs, &refs) < 0) { - return -1; - } + free_refspecs(&remote->active_refspecs); - error = dwim_refspecs(&remote->refspecs, &refs); + error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &refs); git_vector_free(&refs); + if (error < 0) return -1; if ((error = git_fetch_negotiate(remote)) < 0) return error; - return git_fetch_download_pack(remote, progress_cb, progress_payload); + return git_fetch_download_pack(remote); +} + +int git_remote_fetch(git_remote *remote) +{ + int error; + + /* Connect and download everything */ + if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) < 0) + return error; + + if ((error = git_remote_download(remote)) < 0) + return error; + + /* We don't need to be connected anymore */ + git_remote_disconnect(remote); + + /* Create "remote/foo" branches for all remote branches */ + return git_remote_update_tips(remote); } static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) @@ -759,7 +857,7 @@ static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, git_vec (!git_reference_is_branch(resolved_ref)) || (error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 || (error = git_refspec_transform_l(&remote_name, spec, git_reference_name(tracking_ref))) < 0) { - /* Not an error if HEAD is orphaned or no tracking branch */ + /* Not an error if HEAD is unborn or no tracking branch */ if (error == GIT_ENOTFOUND) error = 0; @@ -947,10 +1045,8 @@ int git_remote_update_tips(git_remote *remote) if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) return -1; - if (git_vector_init(&refs, 16, NULL) < 0) - return -1; - if ((error = git_remote_ls(remote, store_refs, &refs)) < 0) + if ((error = ls_to_vector(&refs, remote)) < 0) goto out; if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { @@ -958,7 +1054,7 @@ int git_remote_update_tips(git_remote *remote) goto out; } - git_vector_foreach(&remote->refspecs, i, spec) { + git_vector_foreach(&remote->active_refspecs, i, spec) { if (spec->push) continue; @@ -967,8 +1063,8 @@ int git_remote_update_tips(git_remote *remote) } out: - git_refspec__free(&tagspec); git_vector_free(&refs); + git_refspec__free(&tagspec); return error; } @@ -1001,9 +1097,6 @@ void git_remote_disconnect(git_remote *remote) void git_remote_free(git_remote *remote) { - git_refspec *spec; - size_t i; - if (remote == NULL) return; @@ -1016,67 +1109,55 @@ void git_remote_free(git_remote *remote) git_vector_free(&remote->refs); - git_vector_foreach(&remote->refspecs, i, spec) { - git_refspec__free(spec); - git__free(spec); - } + free_refspecs(&remote->refspecs); git_vector_free(&remote->refspecs); + free_refspecs(&remote->active_refspecs); + git_vector_free(&remote->active_refspecs); + git__free(remote->url); git__free(remote->pushurl); git__free(remote->name); git__free(remote); } -struct cb_data { - git_vector *list; - regex_t *preg; -}; - -static int remote_list_cb(const git_config_entry *entry, void *data_) +static int remote_list_cb(const git_config_entry *entry, void *payload) { - struct cb_data *data = (struct cb_data *)data_; - size_t nmatch = 2; - regmatch_t pmatch[2]; - const char *name = entry->name; + git_vector *list = payload; + const char *name = entry->name + strlen("remote."); + size_t namelen = strlen(name); + char *remote_name; - if (!regexec(data->preg, name, nmatch, pmatch, 0)) { - char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); - GITERR_CHECK_ALLOC(remote_name); + /* we know name matches "remote..(push)?url" */ - if (git_vector_insert(data->list, remote_name) < 0) - return -1; - } + if (!strcmp(&name[namelen - 4], ".url")) + remote_name = git__strndup(name, namelen - 4); /* strip ".url" */ + else + remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */ + GITERR_CHECK_ALLOC(remote_name); - return 0; + return git_vector_insert(list, remote_name); } int git_remote_list(git_strarray *remotes_list, git_repository *repo) { git_config *cfg; git_vector list; - regex_t preg; - struct cb_data data; int error; if (git_repository_config__weakptr(&cfg, repo) < 0) return -1; - if (git_vector_init(&list, 4, NULL) < 0) + if (git_vector_init(&list, 4, git__strcmp_cb) < 0) return -1; - if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) { - giterr_set(GITERR_OS, "Remote catch regex failed to compile"); - return -1; - } + error = git_config_foreach_match( + cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); - data.list = &list; - data.preg = &preg; - error = git_config_foreach(cfg, remote_list_cb, &data); - regfree(&preg); if (error < 0) { size_t i; char *elem; + git_vector_foreach(&list, i, elem) { git__free(elem); } @@ -1090,6 +1171,8 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) return error; } + git_vector_uniq(&list, git__free); + remotes_list->strings = (char **)list.contents; remotes_list->count = list.length; @@ -1103,7 +1186,7 @@ void git_remote_check_cert(git_remote *remote, int check) remote->check_cert = check; } -int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks) +int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks) { assert(remote && callbacks); @@ -1112,7 +1195,7 @@ int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); if (remote->transport && remote->transport->set_callbacks) - remote->transport->set_callbacks(remote->transport, + return remote->transport->set_callbacks(remote->transport, remote->callbacks.progress, NULL, remote->callbacks.payload); @@ -1120,17 +1203,6 @@ int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks return 0; } -void git_remote_set_cred_acquire_cb( - git_remote *remote, - git_cred_acquire_cb cred_acquire_cb, - void *payload) -{ - assert(remote); - - remote->cred_acquire_cb = cred_acquire_cb; - remote->cred_acquire_payload = payload; -} - int git_remote_set_transport(git_remote *remote, git_transport *transport) { assert(remote && transport); @@ -1418,12 +1490,12 @@ int git_remote_rename( int git_remote_update_fetchhead(git_remote *remote) { - return remote->update_fetchhead; + return (remote->update_fetchhead != 0); } void git_remote_set_update_fetchhead(git_remote *remote, int value) { - remote->update_fetchhead = value; + remote->update_fetchhead = (value != 0); } int git_remote_is_valid_name( @@ -1451,7 +1523,7 @@ git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refnam git_refspec *spec; size_t i; - git_vector_foreach(&remote->refspecs, i, spec) { + git_vector_foreach(&remote->active_refspecs, i, spec) { if (spec->push) continue; @@ -1467,7 +1539,7 @@ git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *re git_refspec *spec; size_t i; - git_vector_foreach(&remote->refspecs, i, spec) { + git_vector_foreach(&remote->active_refspecs, i, spec) { if (spec->push) continue; @@ -1490,17 +1562,71 @@ void git_remote_clear_refspecs(git_remote *remote) git_vector_clear(&remote->refspecs); } +static int add_and_dwim(git_remote *remote, const char *str, int push) +{ + git_refspec *spec; + git_vector *vec; + + if (add_refspec(remote, str, !push) < 0) + return -1; + + vec = &remote->refspecs; + spec = git_vector_get(vec, vec->length - 1); + return git_refspec__dwim_one(&remote->active_refspecs, spec, &remote->refs); +} + int git_remote_add_fetch(git_remote *remote, const char *refspec) { - return add_refspec(remote, refspec, true); + return add_and_dwim(remote, refspec, false); } int git_remote_add_push(git_remote *remote, const char *refspec) { - return add_refspec(remote, refspec, false); + return add_and_dwim(remote, refspec, true); } -static int copy_refspecs(git_strarray *array, git_remote *remote, int push) +static int set_refspecs(git_remote *remote, git_strarray *array, int push) +{ + git_vector *vec = &remote->refspecs; + git_refspec *spec; + size_t i; + + /* Start by removing any refspecs of the same type */ + for (i = 0; i < vec->length; i++) { + spec = git_vector_get(vec, i); + if (spec->push != push) + continue; + + git_refspec__free(spec); + git__free(spec); + git_vector_remove(vec, i); + i--; + } + + /* And now we add the new ones */ + + for (i = 0; i < array->count; i++) { + if (add_refspec(remote, array->strings[i], !push) < 0) + return -1; + } + + free_refspecs(&remote->active_refspecs); + git_vector_clear(&remote->active_refspecs); + + return dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs); +} + +int git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array) +{ + return set_refspecs(remote, array, false); +} + +int git_remote_set_push_refspecs(git_remote *remote, git_strarray *array) +{ + return set_refspecs(remote, array, true); +} + +static int copy_refspecs(git_strarray *array, git_remote *remote, unsigned int push) { size_t i; git_vector refspecs; @@ -1555,18 +1681,3 @@ const git_refspec *git_remote_get_refspec(git_remote *remote, size_t n) { return git_vector_get(&remote->refspecs, n); } - -int git_remote_remove_refspec(git_remote *remote, size_t n) -{ - git_refspec *spec; - - assert(remote); - - spec = git_vector_get(&remote->refspecs, n); - if (spec) { - git_refspec__free(spec); - git__free(spec); - } - - return git_vector_remove(&remote->refspecs, n); -} diff --git a/src/remote.h b/src/remote.h index dce4803ed..4164a14b3 100644 --- a/src/remote.h +++ b/src/remote.h @@ -21,16 +21,15 @@ struct git_remote { char *pushurl; git_vector refs; git_vector refspecs; - git_cred_acquire_cb cred_acquire_cb; - void *cred_acquire_payload; + git_vector active_refspecs; git_transport *transport; git_repository *repo; git_remote_callbacks callbacks; git_transfer_progress stats; unsigned int need_pack; git_remote_autotag_option_t download_tags; - unsigned int check_cert; - unsigned int update_fetchhead; + int check_cert; + int update_fetchhead; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/repository.c b/src/repository.c index ed9469c59..dcc02e4fb 100644 --- a/src/repository.c +++ b/src/repository.c @@ -33,8 +33,6 @@ #define GIT_REPO_VERSION 0 -#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates" - static void set_odb(git_repository *repo, git_odb *odb) { if (odb) { @@ -266,7 +264,7 @@ static int find_ceiling_dir_offset( buf[--len] = '\0'; if (!strncmp(path, buf2, len) && - path[len] == '/' && + (path[len] == '/' || !path[len]) && len > max_len) { max_len = len; @@ -322,17 +320,18 @@ static int find_repo( git_buf path = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; - bool try_with_dot_git = false; + bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0); int ceiling_offset; git_buf_free(repo_path); - if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0) + if ((error = git_path_prettify(&path, start_path, NULL)) < 0) return error; ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); - if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) + if (!try_with_dot_git && + (error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) return error; while (!error && !git_buf_len(repo_path)) { @@ -384,7 +383,7 @@ static int find_repo( try_with_dot_git = !try_with_dot_git; } - if (!error && parent_path != NULL) { + if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { if (!git_buf_len(repo_path)) git_buf_clear(parent_path); else { @@ -460,7 +459,9 @@ int git_repository_open_ext( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); - if ((error = load_config_data(repo)) < 0 || + if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) + repo->is_bare = 1; + else if ((error = load_config_data(repo)) < 0 || (error = load_workdir(repo, &parent)) < 0) { git_repository_free(repo); @@ -815,7 +816,7 @@ static int repo_init_create_head(const char *git_dir, const char *ref_name) const char *fmt; if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || - git_filebuf_open(&ref, ref_path.ptr, 0) < 0) + git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE) < 0) goto fail; if (!ref_name) @@ -827,7 +828,7 @@ static int repo_init_create_head(const char *git_dir, const char *ref_name) fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; if (git_filebuf_printf(&ref, fmt, ref_name) < 0 || - git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) + git_filebuf_commit(&ref) < 0) goto fail; git_buf_free(&ref_path); @@ -842,10 +843,6 @@ fail: static bool is_chmod_supported(const char *file_path) { struct stat st1, st2; - static int _is_supported = -1; - - if (_is_supported > -1) - return _is_supported; if (p_stat(file_path, &st1) < 0) return false; @@ -856,27 +853,19 @@ static bool is_chmod_supported(const char *file_path) if (p_stat(file_path, &st2) < 0) return false; - _is_supported = (st1.st_mode != st2.st_mode); - - return _is_supported; + return (st1.st_mode != st2.st_mode); } static bool is_filesystem_case_insensitive(const char *gitdir_path) { git_buf path = GIT_BUF_INIT; - static int _is_insensitive = -1; + int is_insensitive = -1; - if (_is_insensitive > -1) - return _is_insensitive; + if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg")) + is_insensitive = git_path_exists(git_buf_cstr(&path)); - if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0) - goto cleanup; - - _is_insensitive = git_path_exists(git_buf_cstr(&path)); - -cleanup: git_buf_free(&path); - return _is_insensitive; + return is_insensitive; } static bool are_symlinks_supported(const char *wd_path) @@ -884,26 +873,77 @@ static bool are_symlinks_supported(const char *wd_path) git_buf path = GIT_BUF_INIT; int fd; struct stat st; - static int _symlinks_supported = -1; + int symlinks_supported = -1; - if (_symlinks_supported > -1) - return _symlinks_supported; - - if ((fd = git_futils_mktmp(&path, wd_path)) < 0 || + if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 || p_close(fd) < 0 || p_unlink(path.ptr) < 0 || p_symlink("testing", path.ptr) < 0 || p_lstat(path.ptr, &st) < 0) - _symlinks_supported = false; + symlinks_supported = false; else - _symlinks_supported = (S_ISLNK(st.st_mode) != 0); + symlinks_supported = (S_ISLNK(st.st_mode) != 0); (void)p_unlink(path.ptr); git_buf_free(&path); - return _symlinks_supported; + return symlinks_supported; } +#ifdef GIT_USE_ICONV + +static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX"; +static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX"; + +/* Check if the platform is decomposing unicode data for us. We will + * emulate core Git and prefer to use precomposed unicode data internally + * on these platforms, composing the decomposed unicode on the fly. + * + * This mainly happens on the Mac where HDFS stores filenames as + * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will + * return decomposed unicode from readdir() even when the actual + * filesystem is storing precomposed unicode. + */ +static bool does_fs_decompose_unicode_paths(const char *wd_path) +{ + git_buf path = GIT_BUF_INIT; + int fd; + bool found_decomposed = false; + char tmp[6]; + + /* Create a file using a precomposed path and then try to find it + * using the decomposed name. If the lookup fails, then we will mark + * that we should precompose unicode for this repository. + */ + if (git_buf_joinpath(&path, wd_path, nfc_file) < 0 || + (fd = p_mkstemp(path.ptr)) < 0) + goto done; + p_close(fd); + + /* record trailing digits generated by mkstemp */ + memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp)); + + /* try to look up as NFD path */ + if (git_buf_joinpath(&path, wd_path, nfd_file) < 0) + goto done; + memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp)); + + found_decomposed = git_path_exists(path.ptr); + + /* remove temporary file (using original precomposed path) */ + if (git_buf_joinpath(&path, wd_path, nfc_file) < 0) + goto done; + memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp)); + + (void)p_unlink(path.ptr); + +done: + git_buf_free(&path); + return found_decomposed; +} + +#endif + static int create_empty_file(const char *path, mode_t mode) { int fd; @@ -921,71 +961,131 @@ static int create_empty_file(const char *path, mode_t mode) return 0; } +static int repo_local_config( + git_config **out, + git_buf *config_dir, + git_repository *repo, + const char *repo_dir) +{ + int error = 0; + git_config *parent; + const char *cfg_path; + + if (git_buf_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; + cfg_path = git_buf_cstr(config_dir); + + /* make LOCAL config if missing */ + if (!git_path_isfile(cfg_path) && + (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) + return error; + + /* if no repo, just open that file directly */ + if (!repo) + return git_config_open_ondisk(out, cfg_path); + + /* otherwise, open parent config and get that level */ + if ((error = git_repository_config__weakptr(&parent, repo)) < 0) + return error; + + if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { + giterr_clear(); + + if (!(error = git_config_add_file_ondisk( + parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false))) + error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); + } + + git_config_free(parent); + + return error; +} + +static int repo_init_fs_configs( + git_config *cfg, + const char *cfg_path, + const char *repo_dir, + const char *work_dir, + bool update_ignorecase) +{ + int error = 0; + + if (!work_dir) + work_dir = repo_dir; + + if ((error = git_config_set_bool( + cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) + return error; + + if (!are_symlinks_supported(work_dir)) { + if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) + giterr_clear(); + + if (update_ignorecase) { + if (is_filesystem_case_insensitive(repo_dir)) { + if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) + giterr_clear(); + } + +#ifdef GIT_USE_ICONV + if ((error = git_config_set_bool( + cfg, "core.precomposeunicode", + does_fs_decompose_unicode_paths(work_dir))) < 0) + return error; +#endif + + return 0; +} + static int repo_init_config( const char *repo_dir, const char *work_dir, - git_repository_init_options *opts) + uint32_t flags, + uint32_t mode) { int error = 0; git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; + bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); + bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); -#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\ + if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) + goto cleanup; + + if (is_reinit && (error = check_repositoryformatversion(config)) < 0) + goto cleanup; + +#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ goto cleanup; } while (0) - if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) - return -1; + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); - if (!git_path_isfile(git_buf_cstr(&cfg_path)) && - create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) { - git_buf_free(&cfg_path); - return -1; - } - - if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { - git_buf_free(&cfg_path); - return -1; - } - - if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 && - (error = check_repositoryformatversion(config)) < 0) + if ((error = repo_init_fs_configs( + config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) goto cleanup; - SET_REPO_CONFIG( - bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); - SET_REPO_CONFIG( - int32, "core.repositoryformatversion", GIT_REPO_VERSION); - SET_REPO_CONFIG( - bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); - - if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) { + if (!is_bare) { SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - if (!are_symlinks_supported(work_dir)) - SET_REPO_CONFIG(bool, "core.symlinks", false); - - if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) SET_REPO_CONFIG(string, "core.worktree", work_dir); - } - else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) { + else if (is_reinit) { if (git_config_delete_entry(config, "core.worktree") < 0) giterr_clear(); } - } else { - if (!are_symlinks_supported(repo_dir)) - SET_REPO_CONFIG(bool, "core.symlinks", false); } - if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) && - is_filesystem_case_insensitive(repo_dir)) - SET_REPO_CONFIG(bool, "core.ignorecase", true); - - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { + if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { SET_REPO_CONFIG(int32, "core.sharedrepository", 1); SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); } - else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) { + else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { SET_REPO_CONFIG(int32, "core.sharedrepository", 2); SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); } @@ -997,6 +1097,41 @@ cleanup: return error; } +static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) +{ + git_repository *smrepo = NULL; + GIT_UNUSED(n); GIT_UNUSED(p); + + if (git_submodule_open(&smrepo, sm) < 0 || + git_repository_reinit_filesystem(smrepo, true) < 0) + giterr_clear(); + git_repository_free(smrepo); + + return 0; +} + +int git_repository_reinit_filesystem(git_repository *repo, int recurse) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + git_config *config = NULL; + const char *repo_dir = git_repository_path(repo); + + if (!(error = repo_local_config(&config, &path, repo, repo_dir))) + error = repo_init_fs_configs( + config, path.ptr, repo_dir, git_repository_workdir(repo), true); + + git_config_free(config); + git_buf_free(&path); + + git_repository__cvar_cache_clear(repo); + + if (!repo->is_bare && recurse) + (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); + + return error; +} + static int repo_write_template( const char *git_dir, bool allow_overwrite, @@ -1131,31 +1266,34 @@ static int repo_init_structure( /* Copy external template if requested */ if (external_tpl) { - git_config *cfg; - const char *tdir; + git_config *cfg = NULL; + const char *tdir = NULL; + bool default_template = false; + git_buf template_buf = GIT_BUF_INIT; if (opts->template_path) tdir = opts->template_path; - else if ((error = git_config_open_default(&cfg)) < 0) - return error; - else { + else if ((error = git_config_open_default(&cfg)) >= 0) { error = git_config_get_string(&tdir, cfg, "init.templatedir"); - - git_config_free(cfg); - - if (error && error != GIT_ENOTFOUND) - return error; - giterr_clear(); - tdir = GIT_TEMPLATE_DIR; } - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); + if (!tdir) { + if (!(error = git_futils_find_template_dir(&template_buf))) + tdir = template_buf.ptr; + default_template = true; + } + + if (tdir) + error = git_futils_cp_r(tdir, repo_dir, + GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | + GIT_CPDIR_SIMPLE_TO_MODE, dmode); + + git_buf_free(&template_buf); + git_config_free(cfg); if (error < 0) { - if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0) + if (!default_template) return error; /* if template was default, ignore error and use internal */ @@ -1382,22 +1520,22 @@ int git_repository_init_ext( opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; error = repo_init_config( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts); + repo_path.ptr, wd_path.ptr, opts->flags, opts->mode); /* TODO: reinitialize the templates */ } else { if (!(error = repo_init_structure( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) && + repo_path.ptr, wd_path.ptr, opts)) && !(error = repo_init_config( - git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts))) + repo_path.ptr, wd_path.ptr, opts->flags, opts->mode))) error = repo_init_create_head( - git_buf_cstr(&repo_path), opts->initial_head); + repo_path.ptr, opts->initial_head); } if (error < 0) goto cleanup; - error = git_repository_open(out, git_buf_cstr(&repo_path)); + error = git_repository_open(out, repo_path.ptr); if (!error && opts->origin_url) error = repo_init_create_origin(*out, opts->origin_url); @@ -1448,10 +1586,10 @@ int git_repository_head(git_reference **head_out, git_repository *repo) error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1); git_reference_free(head); - return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error; + return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; } -int git_repository_head_orphan(git_repository *repo) +int git_repository_head_unborn(git_repository *repo) { git_reference *ref = NULL; int error; @@ -1459,7 +1597,7 @@ int git_repository_head_orphan(git_repository *repo) error = git_repository_head(&ref, repo); git_reference_free(ref); - if (error == GIT_EORPHANEDHEAD) + if (error == GIT_EUNBORNBRANCH) return 1; if (error < 0) @@ -1492,24 +1630,20 @@ static int repo_contains_no_reference(git_repository *repo) int git_repository_is_empty(git_repository *repo) { git_reference *head = NULL; - int error; + int is_empty = 0; if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC)) - goto cleanup; + if (git_reference_type(head) == GIT_REF_SYMBOLIC) + is_empty = + (strcmp(git_reference_symbolic_target(head), + GIT_REFS_HEADS_DIR "master") == 0) && + repo_contains_no_reference(repo); - if (!(error = strcmp( - git_reference_symbolic_target(head), - GIT_REFS_HEADS_DIR "master") == 0)) - goto cleanup; - - error = repo_contains_no_reference(repo); - -cleanup: git_reference_free(head); - return error < 0 ? -1 : error; + + return is_empty; } const char *git_repository_path(git_repository *repo) @@ -1650,7 +1784,7 @@ int git_repository_hashfile( const char *as_path) { int error; - git_vector filters = GIT_VECTOR_INIT; + git_filter_list *fl = NULL; git_file fd = -1; git_off_t len; git_buf full_path = GIT_BUF_INIT; @@ -1672,7 +1806,8 @@ int git_repository_hashfile( /* passing empty string for "as_path" indicated --no-filters */ if (strlen(as_path) > 0) { - error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB); + error = git_filter_list_load( + &fl, repo, NULL, as_path, GIT_FILTER_TO_ODB); if (error < 0) return error; } else { @@ -1699,12 +1834,12 @@ int git_repository_hashfile( goto cleanup; } - error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters); + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); cleanup: if (fd >= 0) p_close(fd); - git_filters_free(&filters); + git_filter_list_free(fl); git_buf_free(&full_path); return error; diff --git a/src/repository.h b/src/repository.h index 12dc50d51..832df3bd2 100644 --- a/src/repository.h +++ b/src/repository.h @@ -37,6 +37,7 @@ typedef enum { GIT_CVAR_IGNORESTAT, /* core.ignorestat */ GIT_CVAR_TRUSTCTIME, /* core.trustctime */ GIT_CVAR_ABBREV, /* core.abbrev */ + GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -86,6 +87,8 @@ typedef enum { GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE, /* core.abbrev */ GIT_ABBREV_DEFAULT = 7, + /* core.precomposeunicode */ + GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, } git_cvar_value; diff --git a/src/reset.c b/src/reset.c index cea212a93..a9780bfbc 100644 --- a/src/reset.c +++ b/src/reset.c @@ -24,10 +24,9 @@ int git_reset_default( { git_object *commit = NULL; git_tree *tree = NULL; - git_diff_list *diff = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i; - git_diff_delta *delta; + size_t i, max_i; git_index_entry entry; int error; git_index *index = NULL; @@ -58,7 +57,9 @@ int git_reset_default( &diff, repo, tree, index, &opts)) < 0) goto cleanup; - git_vector_foreach(&diff->deltas, i, delta) { + for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) { + const git_diff_delta *delta = git_diff_get_delta(diff, i); + if ((error = git_index_conflict_remove(index, delta->old_file.path)) < 0) goto cleanup; @@ -85,7 +86,7 @@ cleanup: git_object_free(commit); git_tree_free(tree); git_index_free(index); - git_diff_list_free(diff); + git_diff_free(diff); return error; } @@ -135,7 +136,7 @@ int git_reset( if (reset_type == GIT_RESET_HARD) { /* overwrite working directory with HEAD */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_SKIP_UNMERGED; if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) goto cleanup; diff --git a/src/revparse.c b/src/revparse.c index bcfb0843f..c120b466f 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -93,11 +93,7 @@ static int revparse_lookup_object( int error; git_reference *ref; - error = maybe_sha(object_out, repo, spec); - if (!error) - return 0; - - if (error < 0 && error != GIT_ENOTFOUND) + if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) return error; error = git_reference_dwim(&ref, repo, spec); @@ -112,24 +108,17 @@ static int revparse_lookup_object( return error; } - if (error < 0 && error != GIT_ENOTFOUND) + if (error != GIT_ENOTFOUND) return error; - error = maybe_abbrev(object_out, repo, spec); - if (!error) - return 0; + if ((strlen(spec) < GIT_OID_HEXSZ) && + ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) + return error; - if (error < 0 && error != GIT_ENOTFOUND) + if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) return error; - error = maybe_describe(object_out, repo, spec); - if (!error) - return 0; - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); + giterr_set(GITERR_REFERENCE, "Revspec '%s' not found.", spec); return GIT_ENOTFOUND; } @@ -171,7 +160,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) goto cleanup; - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) goto cleanup; numentries = git_reflog_entrycount(reflog); @@ -219,7 +208,7 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide const git_reflog_entry *entry; bool search_by_pos = (identifier <= 100000000); - if (git_reflog_read(&reflog, ref) < 0) + if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) return -1; numentries = git_reflog_entrycount(reflog); @@ -228,7 +217,7 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t ide if (numentries < identifier + 1) { giterr_set( GITERR_REFERENCE, - "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ, + "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, git_reference_name(ref), numentries, identifier); error = GIT_ENOTFOUND; @@ -685,6 +674,8 @@ int revparse__ext( git_reference *reference = NULL; git_object *base_rev = NULL; + bool should_return_reference = true; + assert(object_out && reference_out && repo && spec); *object_out = NULL; @@ -693,6 +684,8 @@ int revparse__ext( while (spec[pos]) { switch (spec[pos]) { case '^': + should_return_reference = false; + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) goto cleanup; @@ -725,6 +718,8 @@ int revparse__ext( { git_object *temp_object = NULL; + should_return_reference = false; + if ((error = extract_how_many(&n, spec, &pos)) < 0) goto cleanup; @@ -743,6 +738,8 @@ int revparse__ext( { git_object *temp_object = NULL; + should_return_reference = false; + if ((error = extract_path(&buf, spec, &pos)) < 0) goto cleanup; @@ -807,6 +804,11 @@ int revparse__ext( if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) goto cleanup; + if (!should_return_reference) { + git_reference_free(reference); + reference = NULL; + } + *object_out = base_rev; *reference_out = reference; *identifier_len_out = identifier_len; @@ -899,13 +901,9 @@ int git_revparse( rstr++; } - if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) { - return error; - } - - if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) { - return error; - } + error = git_revparse_single(&revspec->from, repo, lstr); + if (!error) + error = git_revparse_single(&revspec->to, repo, rstr); git__free((void*)lstr); } else { diff --git a/src/revwalk.c b/src/revwalk.c index 528d02b20..3dd14b419 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -14,8 +14,6 @@ #include "git2/revparse.h" #include "merge.h" -#include - git_commit_list_node *git_revwalk__commit_lookup( git_revwalk *walk, const git_oid *oid) { @@ -41,28 +39,50 @@ git_commit_list_node *git_revwalk__commit_lookup( return commit; } -static void mark_uninteresting(git_commit_list_node *commit) +static int mark_uninteresting(git_commit_list_node *commit) { unsigned short i; + git_array_t(git_commit_list_node *) pending = GIT_ARRAY_INIT; + git_commit_list_node **tmp; + assert(commit); - commit->uninteresting = 1; + git_array_alloc(pending); + GITERR_CHECK_ARRAY(pending); - /* This means we've reached a merge base, so there's no need to walk any more */ - if ((commit->flags & (RESULT | STALE)) == RESULT) - return; + do { + commit->uninteresting = 1; - for (i = 0; i < commit->out_degree; ++i) - if (!commit->parents[i]->uninteresting) - mark_uninteresting(commit->parents[i]); + /* This means we've reached a merge base, so there's no need to walk any more */ + if ((commit->flags & (RESULT | STALE)) == RESULT) { + tmp = git_array_pop(pending); + commit = tmp ? *tmp : NULL; + continue; + } + + for (i = 0; i < commit->out_degree; ++i) + if (!commit->parents[i]->uninteresting) { + git_commit_list_node **node = git_array_alloc(pending); + GITERR_CHECK_ALLOC(node); + *node = commit->parents[i]; + } + + tmp = git_array_pop(pending); + commit = tmp ? *tmp : NULL; + + } while (git_array_size(pending) > 0); + + git_array_clear(pending); + + return 0; } static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide) { int error; - if (hide) - mark_uninteresting(commit); + if (hide && mark_uninteresting(commit) < 0) + return -1; if (commit->seen) return 0; @@ -77,10 +97,14 @@ static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int h static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit) { - unsigned short i; + unsigned short i, max; int error = 0; - for (i = 0; i < commit->out_degree && !error; ++i) + max = commit->out_degree; + if (walk->first_parent && commit->out_degree) + max = 1; + + for (i = 0; i < max && !error; ++i) error = process_commit(walk, commit->parents[i], commit->uninteresting); return error; @@ -155,48 +179,35 @@ static int push_glob_cb(const char *refname, void *data_) static int push_glob(git_revwalk *walk, const char *glob, int hide) { + int error = 0; git_buf buf = GIT_BUF_INIT; struct push_cb_data data; - regex_t preg; + size_t wildcard; assert(walk && glob); /* refs/ is implied if not given in the glob */ - if (strncmp(glob, GIT_REFS_DIR, strlen(GIT_REFS_DIR))) { - git_buf_printf(&buf, GIT_REFS_DIR "%s", glob); - } else { + if (git__prefixcmp(glob, GIT_REFS_DIR) != 0) + git_buf_joinpath(&buf, GIT_REFS_DIR, glob); + else git_buf_puts(&buf, glob); - } /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ - memset(&preg, 0x0, sizeof(regex_t)); - if (regcomp(&preg, "[?*[]", REG_EXTENDED)) { - giterr_set(GITERR_OS, "Regex failed to compile"); - git_buf_free(&buf); - return -1; - } - - if (regexec(&preg, glob, 0, NULL, 0)) - git_buf_puts(&buf, "/*"); - - if (git_buf_oom(&buf)) - goto on_error; + wildcard = strcspn(glob, "?*["); + if (!glob[wildcard]) + git_buf_put(&buf, "/*", 2); data.walk = walk; data.hide = hide; - if (git_reference_foreach_glob( - walk->repo, git_buf_cstr(&buf), push_glob_cb, &data) < 0) - goto on_error; + if (git_buf_oom(&buf)) + error = -1; + else + error = git_reference_foreach_glob( + walk->repo, git_buf_cstr(&buf), push_glob_cb, &data); - regfree(&preg); git_buf_free(&buf); - return 0; - -on_error: - regfree(&preg); - git_buf_free(&buf); - return -1; + return error; } int git_revwalk_push_glob(git_revwalk *walk, const char *glob) @@ -311,7 +322,7 @@ static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) { git_commit_list_node *next; - unsigned short i; + unsigned short i, max; for (;;) { next = git_commit_list_pop(&walk->iterator_topo); @@ -325,7 +336,12 @@ static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk continue; } - for (i = 0; i < next->out_degree; ++i) { + + max = next->out_degree; + if (walk->first_parent && next->out_degree) + max = 1; + + for (i = 0; i < max; ++i) { git_commit_list_node *parent = next->parents[i]; if (--parent->in_degree == 0 && parent->topo_delay) { @@ -483,6 +499,11 @@ void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) } } +void git_revwalk_simplify_first_parent(git_revwalk *walk) +{ + walk->first_parent = 1; +} + int git_revwalk_next(git_oid *oid, git_revwalk *walk) { int error; diff --git a/src/revwalk.h b/src/revwalk.h index 22696dfcd..8c821d098 100644 --- a/src/revwalk.h +++ b/src/revwalk.h @@ -31,7 +31,8 @@ struct git_revwalk { int (*get_next)(git_commit_list_node **, git_revwalk *); int (*enqueue)(git_revwalk *, git_commit_list_node *); - unsigned walking:1; + unsigned walking:1, + first_parent: 1; unsigned int sorting; /* merge base calculation */ diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index b7e66cc69..c6b561340 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -9,6 +9,7 @@ #include "sha1_lookup.h" #include "common.h" +#include "oid.h" /* * Conventional binary search loop looks like this: @@ -108,7 +109,54 @@ int sha1_entry_pos(const void *table, * byte 0 thru (ofs-1) are the same between * lo and hi; ofs is the first byte that is * different. + * + * If ofs==20, then no bytes are different, + * meaning we have entries with duplicate + * keys. We know that we are in a solid run + * of this entry (because the entries are + * sorted, and our lo and hi are the same, + * there can be nothing but this single key + * in between). So we can stop the search. + * Either one of these entries is it (and + * we do not care which), or we do not have + * it. + * + * Furthermore, we know that one of our + * endpoints must be the edge of the run of + * duplicates. For example, given this + * sequence: + * + * idx 0 1 2 3 4 5 + * key A C C C C D + * + * If we are searching for "B", we might + * hit the duplicate run at lo=1, hi=3 + * (e.g., by first mi=3, then mi=0). But we + * can never have lo > 1, because B < C. + * That is, if our key is less than the + * run, we know that "lo" is the edge, but + * we can say nothing of "hi". Similarly, + * if our key is greater than the run, we + * know that "hi" is the edge, but we can + * say nothing of "lo". + * + * Therefore if we do not find it, we also + * know where it would go if it did exist: + * just on the far side of the edge that we + * know about. */ + if (ofs == 20) { + mi = lo; + mi_key = base + elem_size * mi + key_offset; + cmp = memcmp(mi_key, key, 20); + if (!cmp) + return mi; + if (cmp < 0) + return -1 - hi; + else + return -1 - lo; + } + hiv = hi_key[ofs_0]; if (ofs_0 < 19) hiv = (hiv << 8) | hi_key[ofs_0+1]; @@ -176,3 +224,26 @@ int sha1_entry_pos(const void *table, } while (lo < hi); return -((int)lo)-1; } + +int sha1_position(const void *table, + size_t stride, + unsigned lo, unsigned hi, + const unsigned char *key) +{ + const unsigned char *base = table; + + do { + unsigned mi = (lo + hi) / 2; + int cmp = git_oid__hashcmp(base + mi * stride, key); + + if (!cmp) + return mi; + + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } while (lo < hi); + + return -((int)lo)-1; +} diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h index 9a3537273..3799620c7 100644 --- a/src/sha1_lookup.h +++ b/src/sha1_lookup.h @@ -15,4 +15,9 @@ int sha1_entry_pos(const void *table, unsigned lo, unsigned hi, unsigned nr, const unsigned char *key); +int sha1_position(const void *table, + size_t stride, + unsigned lo, unsigned hi, + const unsigned char *key); + #endif diff --git a/src/signature.c b/src/signature.c index 0a34ccfaa..ec51a42e9 100644 --- a/src/signature.c +++ b/src/signature.c @@ -74,7 +74,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema git_signature_free(p); return signature_error("Signature cannot have an empty name"); } - + p->when.time = time; p->when.offset = offset; @@ -84,8 +84,12 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema git_signature *git_signature_dup(const git_signature *sig) { - git_signature *new = git__calloc(1, sizeof(git_signature)); + git_signature *new; + if (sig == NULL) + return NULL; + + new = git__calloc(1, sizeof(git_signature)); if (new == NULL) return NULL; @@ -129,6 +133,23 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema return 0; } +int git_signature_default(git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *user_name, *user_email; + + if ((error = git_repository_config(&cfg, repo)) < 0) + return error; + + if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && + !(error = git_config_get_string(&user_email, cfg, "user.email"))) + error = git_signature_now(out, user_name, user_email); + + git_config_free(cfg); + return error; +} + int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { diff --git a/src/sortedcache.c b/src/sortedcache.c new file mode 100644 index 000000000..466e55dbe --- /dev/null +++ b/src/sortedcache.c @@ -0,0 +1,380 @@ +#include "sortedcache.h" + +GIT__USE_STRMAP; + +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path) +{ + git_sortedcache *sc; + size_t pathlen; + + pathlen = path ? strlen(path) : 0; + + sc = git__calloc(sizeof(git_sortedcache) + pathlen + 1, 1); + GITERR_CHECK_ALLOC(sc); + + if (git_pool_init(&sc->pool, 1, 0) < 0 || + git_vector_init(&sc->items, 4, item_cmp) < 0 || + (sc->map = git_strmap_alloc()) == NULL) + goto fail; + + if (git_rwlock_init(&sc->lock)) { + giterr_set(GITERR_OS, "Failed to initialize lock"); + goto fail; + } + + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; + sc->free_item_payload = free_item_payload; + GIT_REFCOUNT_INC(sc); + if (pathlen) + memcpy(sc->path, path, pathlen); + + *out = sc; + return 0; + +fail: + if (sc->map) + git_strmap_free(sc->map); + git_vector_free(&sc->items); + git_pool_clear(&sc->pool); + git__free(sc); + return -1; +} + +void git_sortedcache_incref(git_sortedcache *sc) +{ + GIT_REFCOUNT_INC(sc); +} + +const char *git_sortedcache_path(git_sortedcache *sc) +{ + return sc->path; +} + +static void sortedcache_clear(git_sortedcache *sc) +{ + git_strmap_clear(sc->map); + + if (sc->free_item) { + size_t i; + void *item; + + git_vector_foreach(&sc->items, i, item) { + sc->free_item(sc->free_item_payload, item); + } + } + + git_vector_clear(&sc->items); + + git_pool_clear(&sc->pool); +} + +static void sortedcache_free(git_sortedcache *sc) +{ + /* acquire write lock to make sure everyone else is done */ + if (git_sortedcache_wlock(sc) < 0) + return; + + sortedcache_clear(sc); + git_vector_free(&sc->items); + git_strmap_free(sc->map); + + git_sortedcache_wunlock(sc); + + git_rwlock_free(&sc->lock); + git__free(sc); +} + +void git_sortedcache_free(git_sortedcache *sc) +{ + if (!sc) + return; + GIT_REFCOUNT_DEC(sc, sortedcache_free); +} + +static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) +{ + git_sortedcache *sc = payload; + /* path will already have been copied by upsert */ + memcpy(tgt_item, src_item, sc->item_path_offset); + return 0; +} + +/* copy a sorted cache */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload) +{ + int error = 0; + git_sortedcache *tgt; + size_t i; + void *src_item, *tgt_item; + + /* just use memcpy if no special copy fn is passed in */ + if (!copy_item) { + copy_item = sortedcache_copy_item; + payload = src; + } + + if ((error = git_sortedcache_new( + &tgt, src->item_path_offset, + src->free_item, src->free_item_payload, + src->items._cmp, src->path)) < 0) + return error; + + if (lock && git_sortedcache_rlock(src) < 0) { + git_sortedcache_free(tgt); + return -1; + } + + git_vector_foreach(&src->items, i, src_item) { + char *path = ((char *)src_item) + src->item_path_offset; + + if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || + (error = copy_item(payload, tgt_item, src_item)) < 0) + break; + } + + if (lock) + git_sortedcache_runlock(src); + if (error) + git_sortedcache_free(tgt); + + *out = !error ? tgt : NULL; + + return error; +} + +/* lock sortedcache while making modifications */ +int git_sortedcache_wlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_wrlock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire write lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done with modifications */ +void git_sortedcache_wunlock(git_sortedcache *sc) +{ + git_vector_sort(&sc->items); + git_rwlock_wrunlock(&sc->lock); +} + +/* lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_rdlock(&sc->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire read lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done reading */ +void git_sortedcache_runlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + git_rwlock_rdunlock(&sc->lock); +} + +/* if the file has changed, lock cache and load file contents into buf; + * returns <0 on error, >0 if file has not changed + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) +{ + int error, fd; + + if ((error = git_sortedcache_wlock(sc)) < 0) + return error; + + if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) + goto unlock; + + if (!git__is_sizet(sc->stamp.size)) { + giterr_set(GITERR_INVALID, "Unable to load file larger than size_t"); + error = -1; + goto unlock; + } + + if ((fd = git_futils_open_ro(sc->path)) < 0) { + error = fd; + goto unlock; + } + + if (buf) + error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size); + + (void)p_close(fd); + + if (error < 0) + goto unlock; + + return 1; /* return 1 -> file needs reload and was successfully loaded */ + +unlock: + git_sortedcache_wunlock(sc); + return error; +} + +void git_sortedcache_updated(git_sortedcache *sc) +{ + /* update filestamp to latest value */ + if (git_futils_filestamp_check(&sc->stamp, sc->path) < 0) + giterr_clear(); +} + +/* release all items in sorted cache */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock) +{ + if (wlock && git_sortedcache_wlock(sc) < 0) + return -1; + + sortedcache_clear(sc); + + if (wlock) + git_sortedcache_wunlock(sc); + + return 0; +} + +/* find and/or insert item, returning pointer to item data */ +int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) +{ + int error = 0; + khiter_t pos; + void *item; + size_t keylen, itemlen; + char *item_key; + + pos = git_strmap_lookup_index(sc->map, key); + if (git_strmap_valid_index(sc->map, pos)) { + item = git_strmap_value_at(sc->map, pos); + goto done; + } + + keylen = strlen(key); + itemlen = sc->item_path_offset + keylen + 1; + itemlen = (itemlen + 7) & ~7; + + if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) { + /* don't use GITERR_CHECK_ALLOC b/c of lock */ + error = -1; + goto done; + } + + /* one strange thing is that even if the vector or hash table insert + * fail, there is no way to free the pool item so we just abandon it + */ + + item_key = ((char *)item) + sc->item_path_offset; + memcpy(item_key, key, keylen); + + pos = kh_put(str, sc->map, item_key, &error); + if (error < 0) + goto done; + + if (!error) + kh_key(sc->map, pos) = item_key; + kh_val(sc->map, pos) = item; + + error = git_vector_insert(&sc->items, item); + if (error < 0) + git_strmap_delete_at(sc->map, pos); + +done: + if (out) + *out = !error ? item : NULL; + return error; +} + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +{ + khiter_t pos = git_strmap_lookup_index(sc->map, key); + if (git_strmap_valid_index(sc->map, pos)) + return git_strmap_value_at(sc->map, pos); + return NULL; +} + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc) +{ + return git_vector_length(&sc->items); +} + +/* lookup item by index */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) +{ + /* make sure the items are sorted so this gets the correct item */ + if (!sc->items.sorted) + git_vector_sort(&sc->items); + + return git_vector_get(&sc->items, pos); +} + +/* helper struct so bsearch callback can know offset + key value for cmp */ +struct sortedcache_magic_key { + size_t offset; + const char *key; +}; + +static int sortedcache_magic_cmp(const void *key, const void *value) +{ + const struct sortedcache_magic_key *magic = key; + const char *value_key = ((const char *)value) + magic->offset; + return strcmp(magic->key, value_key); +} + +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key) +{ + struct sortedcache_magic_key magic; + + magic.offset = sc->item_path_offset; + magic.key = key; + + return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); +} + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos) +{ + char *item; + khiter_t mappos; + + /* because of pool allocation, this can't actually remove the item, + * but we can remove it from the items vector and the hash table. + */ + + if ((item = git_vector_get(&sc->items, pos)) == NULL) { + giterr_set(GITERR_INVALID, "Removing item out of range"); + return GIT_ENOTFOUND; + } + + (void)git_vector_remove(&sc->items, pos); + + mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset); + git_strmap_delete_at(sc->map, mappos); + + if (sc->free_item) + sc->free_item(sc->free_item_payload, item); + + return 0; +} + diff --git a/src/sortedcache.h b/src/sortedcache.h new file mode 100644 index 000000000..4cacad62b --- /dev/null +++ b/src/sortedcache.h @@ -0,0 +1,178 @@ +/* + * 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_sorted_cache_h__ +#define INCLUDE_sorted_cache_h__ + +#include "util.h" +#include "fileops.h" +#include "vector.h" +#include "thread-utils.h" +#include "pool.h" +#include "strmap.h" + +#include + +/* + * The purpose of this data structure is to cache the parsed contents of a + * file (a.k.a. the backing file) where each item in the file can be + * identified by a key string and you want to both look them up by name + * and traverse them in sorted order. Each item is assumed to itself end + * in a GIT_FLEX_ARRAY. + */ + +typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); + +typedef struct { + git_refcount rc; + git_rwlock lock; + size_t item_path_offset; + git_sortedcache_free_item_fn free_item; + void *free_item_payload; + git_pool pool; + git_vector items; + git_strmap *map; + git_futils_filestamp stamp; + char path[GIT_FLEX_ARRAY]; +} git_sortedcache; + +/* Create a new sortedcache + * + * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * the end containing their key string, you have to provide the item_cmp + * sorting function because the sorting function doesn't get a payload + * and therefore can't know the offset to the item key string. :-( + * + * @param out The allocated git_sortedcache + * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the + * struct - use offsetof(struct mine, key-field) to get this + * @param free_item Optional callback to free each item + * @param free_item_payload Optional payload passed to free_item callback + * @param item_cmp Compare the keys of two items + * @param path The path to the backing store file for this cache; this + * may be NULL. The cache makes it easy to load this and check + * if it has been modified since the last load and/or write. + */ +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, /* use offsetof(struct, path-field) macro */ + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path); + +/* Copy a sorted cache + * + * - `copy_item` can be NULL to just use memcpy + * - if `lock`, grabs read lock on `src` during copy and releases after + */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload); + +/* Free sorted cache (first calling `free_item` callbacks) + * + * Don't call on a locked collection - it may acquire a write lock + */ +void git_sortedcache_free(git_sortedcache *sc); + +/* Increment reference count - balance with call to free */ +void git_sortedcache_incref(git_sortedcache *sc); + +/* Get the pathname associated with this cache at creation time */ +const char *git_sortedcache_path(git_sortedcache *sc); + +/* + * CACHE WRITE FUNCTIONS + * + * The following functions require you to have a writer lock to make the + * modification. Some of the functions take a `wlock` parameter and + * will optionally lock and unlock for you if that is passed as true. + * + */ + +/* Lock sortedcache for write */ +int git_sortedcache_wlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with write */ +void git_sortedcache_wunlock(git_sortedcache *sc); + +/* Lock cache and load backing file into a buffer. + * + * This grabs a write lock on the cache then looks at the modification + * time and size of the file on disk. + * + * If the file appears to have changed, this loads the file contents into + * the buffer and returns a positive value leaving the cache locked - the + * caller should parse the file content, update the cache as needed, then + * release the lock. NOTE: In this case, the caller MUST unlock the cache. + * + * If the file appears to be unchanged, then this automatically releases + * the lock on the cache, clears the buffer, and returns 0. + * + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf); + +/* Refresh file timestamp after write completes + * You should already be holding the write lock when you call this. + */ +void git_sortedcache_updated(git_sortedcache *sc); + +/* Release all items in sorted cache + * + * If `wlock` is true, grabs write lock and releases when done, otherwise + * you should already be holding a write lock when you call this. + */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock); + +/* Find and/or insert item, returning pointer to item data. + * You should already be holding the write lock when you call this. + */ +int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key); + +/* Removes entry at pos from cache + * You should already be holding the write lock when you call this. + */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos); + +/* + * CACHE READ FUNCTIONS + * + * The following functions access items in the cache. To prevent the + * results from being invalidated before they can be used, you should be + * holding either a read lock or a write lock when using these functions. + * + */ + +/* Lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with read */ +void git_sortedcache_runlock(git_sortedcache *sc); + +/* Lookup item by key - returns NULL if not found */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); + +/* Get how many items are in the cache + * + * You can call this function without holding a lock, but be aware + * that it may change before you use it. + */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc); + +/* Lookup item by index - returns NULL if out of range */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); + +/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key); + +#endif diff --git a/src/stash.c b/src/stash.c index 1222634d5..083c2a4cd 100644 --- a/src/stash.c +++ b/src/stash.c @@ -27,7 +27,7 @@ static int retrieve_head(git_reference **out, git_repository *repo) { int error = git_repository_head(out, repo); - if (error == GIT_EORPHANEDHEAD) + if (error == GIT_EUNBORNBRANCH) return create_error(error, "You do not have the initial commit yet."); return error; @@ -117,7 +117,7 @@ static int build_tree_from_index(git_tree **out, git_index *index) static int commit_index( git_commit **i_commit, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, const git_commit *parent) { @@ -153,65 +153,61 @@ cleanup: return error; } -struct cb_data { - git_index *index; - - int error; - +struct stash_update_rules { bool include_changed; bool include_untracked; bool include_ignored; }; -static int update_index_cb( - const git_diff_delta *delta, - float progress, - void *payload) +static int stash_update_index_from_diff( + git_index *index, + const git_diff *diff, + struct stash_update_rules *data) { - struct cb_data *data = (struct cb_data *)payload; - const char *add_path = NULL; + int error = 0; + size_t d, max_d = git_diff_num_deltas(diff); - GIT_UNUSED(progress); + for (d = 0; !error && d < max_d; ++d) { + const char *add_path = NULL; + const git_diff_delta *delta = git_diff_get_delta(diff, d); - switch (delta->status) { - case GIT_DELTA_IGNORED: - if (data->include_ignored) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_UNTRACKED: - if (data->include_untracked) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - if (data->include_changed) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_DELETED: - if (!data->include_changed) + switch (delta->status) { + case GIT_DELTA_IGNORED: + if (data->include_ignored) + add_path = delta->new_file.path; break; - if (git_index_find(NULL, data->index, delta->old_file.path) == 0) - data->error = git_index_remove( - data->index, delta->old_file.path, 0); - break; - default: - /* Unimplemented */ - giterr_set( - GITERR_INVALID, - "Cannot update index. Unimplemented status (%d)", - delta->status); - data->error = -1; - break; + case GIT_DELTA_UNTRACKED: + if (data->include_untracked) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + if (data->include_changed) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_DELETED: + if (data->include_changed && + !git_index_find(NULL, index, delta->old_file.path)) + error = git_index_remove(index, delta->old_file.path, 0); + break; + + default: + /* Unimplemented */ + giterr_set( + GITERR_INVALID, + "Cannot update index. Unimplemented status (%d)", + delta->status); + return -1; + } + + if (add_path != NULL) + error = git_index_add_bypath(index, add_path); } - if (add_path != NULL) - data->error = git_index_add_bypath(data->index, add_path); - - return data->error; + return error; } static int build_untracked_tree( @@ -221,15 +217,13 @@ static int build_untracked_tree( uint32_t flags) { git_tree *i_tree = NULL; - git_diff_list *diff = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct cb_data data = {0}; + struct stash_update_rules data = {0}; int error; git_index_clear(index); - data.index = index; - if (flags & GIT_STASH_INCLUDE_UNTRACKED) { opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; @@ -248,18 +242,13 @@ static int build_untracked_tree( &diff, git_index_owner(index), i_tree, &opts)) < 0) goto cleanup; - if ((error = git_diff_foreach( - diff, update_index_cb, NULL, NULL, &data)) < 0) - { - if (error == GIT_EUSER) - error = data.error; + if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) goto cleanup; - } error = build_tree_from_index(tree_out, index); cleanup: - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(i_tree); return error; } @@ -267,7 +256,7 @@ cleanup: static int commit_untracked( git_commit **u_commit, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, git_commit *i_commit, uint32_t flags) @@ -311,41 +300,29 @@ static int build_workdir_tree( { git_repository *repo = git_index_owner(index); git_tree *b_tree = NULL; - git_diff_list *diff = NULL, *diff2 = NULL; + git_diff *diff = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct cb_data data = {0}; + struct stash_update_rules data = {0}; int error; + opts.flags = GIT_DIFF_IGNORE_SUBMODULES; + if ((error = git_commit_tree(&b_tree, b_commit)) < 0) goto cleanup; - if ((error = git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts)) < 0) + if ((error = git_diff_tree_to_workdir_with_index( + &diff, repo, b_tree, &opts)) < 0) goto cleanup; - if ((error = git_diff_index_to_workdir(&diff2, repo, NULL, &opts)) < 0) - goto cleanup; - - if ((error = git_diff_merge(diff, diff2)) < 0) - goto cleanup; - - data.index = index; data.include_changed = true; - if ((error = git_diff_foreach( - diff, update_index_cb, NULL, NULL, &data)) < 0) - { - if (error == GIT_EUSER) - error = data.error; + if ((error = stash_update_index_from_diff(index, diff, &data)) < 0) goto cleanup; - } - - if ((error = build_tree_from_index(tree_out, index)) < 0) - goto cleanup; + error = build_tree_from_index(tree_out, index); cleanup: - git_diff_list_free(diff); - git_diff_list_free(diff2); + git_diff_free(diff); git_tree_free(b_tree); return error; @@ -354,7 +331,7 @@ cleanup: static int commit_worktree( git_oid *w_commit_oid, git_index *index, - git_signature *stasher, + const git_signature *stasher, const char *message, git_commit *i_commit, git_commit *b_commit, @@ -431,17 +408,19 @@ cleanup: static int update_reflog( git_oid *w_commit_oid, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message) { - git_reference *stash = NULL; + git_reference *stash; git_reflog *reflog = NULL; int error; if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + git_reference_free(stash); + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE) < 0)) goto cleanup; if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0) @@ -451,7 +430,6 @@ static int update_reflog( goto cleanup; cleanup: - git_reference_free(stash); git_reflog_free(reflog); return error; } @@ -474,12 +452,14 @@ static int ensure_there_are_changes_to_stash( git_status_options opts = GIT_STATUS_OPTIONS_INIT; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + if (include_untracked_files) - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; if (include_ignored_files) - opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED; + opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); @@ -510,7 +490,7 @@ static int reset_index_and_workdir( int git_stash_save( git_oid *out, git_repository *repo, - git_signature *stasher, + const git_signature *stasher, const char *message, uint32_t flags) { @@ -595,7 +575,7 @@ int git_stash_foreach( if (error < 0) goto cleanup; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); @@ -629,7 +609,7 @@ int git_stash_drop( if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) return error; - if ((error = git_reflog_read(&reflog, stash)) < 0) + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) goto cleanup; max = git_reflog_entrycount(reflog); diff --git a/src/status.c b/src/status.c index e520c1017..07fdcb5c3 100644 --- a/src/status.c +++ b/src/status.c @@ -52,7 +52,7 @@ static unsigned int index_delta2status(const git_diff_delta *head2idx) } static unsigned int workdir_delta2status( - git_diff_list *diff, git_diff_delta *idx2wd) + git_diff *diff, git_diff_delta *idx2wd) { git_status_t st = GIT_STATUS_CURRENT; @@ -225,24 +225,6 @@ static git_status_list *git_status_list_alloc(git_index *index) return status; } -/* -static int newfile_cmp(const void *a, const void *b) -{ - const git_diff_delta *delta_a = a; - const git_diff_delta *delta_b = b; - - return git__strcmp(delta_a->new_file.path, delta_b->new_file.path); -} - -static int newfile_casecmp(const void *a, const void *b) -{ - const git_diff_delta *delta_a = a; - const git_diff_delta *delta_b = b; - - return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); -} -*/ - int git_status_list_new( git_status_list **out, git_repository *repo, @@ -251,14 +233,14 @@ int git_status_list_new( git_index *index = NULL; git_status_list *status = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; + git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; int error = 0; unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; - assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + assert(show <= GIT_STATUS_SHOW_WORKDIR_ONLY); *out = NULL; @@ -269,12 +251,17 @@ int git_status_list_new( return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if (((error = git_repository_head_tree(&head, repo)) < 0) && - error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) { - git_index_free(index); /* release index */ - return error; + if ((error = git_repository_head_tree(&head, repo)) < 0) { + if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) + goto done; + giterr_clear(); } + /* refresh index from disk unless prevented */ + if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && + git_index_read(index, false) < 0) + giterr_clear(); + status = git_status_list_alloc(index); GITERR_CHECK_ALLOC(status); @@ -284,6 +271,7 @@ int git_status_list_new( } diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; @@ -300,37 +288,32 @@ int git_status_list_new( if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; + if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) + findopt.flags = findopt.flags | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES | + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { if ((error = git_diff_tree_to_index( - &status->head2idx, repo, head, NULL, &diffopt)) < 0) + &status->head2idx, repo, head, index, &diffopt)) < 0) goto done; if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(status->head2idx, NULL)) < 0) + (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) goto done; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, NULL, &diffopt)) < 0) + &status->idx2wd, repo, index, &diffopt)) < 0) goto done; if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0) + (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) goto done; } - if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((error = git_diff__paired_foreach( - status->head2idx, NULL, status_collect, status)) < 0) - goto done; - - git_diff_list_free(status->head2idx); - status->head2idx = NULL; - } - if ((error = git_diff__paired_foreach( status->head2idx, status->idx2wd, status_collect, status)) < 0) goto done; @@ -383,8 +366,8 @@ void git_status_list_free(git_status_list *status) if (status == NULL) return; - git_diff_list_free(status->head2idx); - git_diff_list_free(status->idx2wd); + git_diff_free(status->head2idx); + git_diff_free(status->idx2wd); git_vector_foreach(&status->paired, i, status_entry) git__free(status_entry); diff --git a/src/status.h b/src/status.h index b58e0ebd6..33008b89c 100644 --- a/src/status.h +++ b/src/status.h @@ -14,8 +14,8 @@ struct git_status_list { git_status_options opts; - git_diff_list *head2idx; - git_diff_list *idx2wd; + git_diff *head2idx; + git_diff *idx2wd; git_vector paired; }; diff --git a/src/strmap.c b/src/strmap.c new file mode 100644 index 000000000..b26a13d1f --- /dev/null +++ b/src/strmap.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "strmap.h" + +int git_strmap_next( + void **data, + git_strmap_iter* iter, + git_strmap *map) +{ + if (!map) + return GIT_ERROR; + + while (*iter != git_strmap_end(map)) { + if (!(git_strmap_has_data(map, *iter))) { + ++(*iter); + continue; + } + + *data = git_strmap_value_at(map, *iter); + + ++(*iter); + + return GIT_OK; + } + + return GIT_ITEROVER; +} diff --git a/src/strmap.h b/src/strmap.h index 44176a0fc..8276ab468 100644 --- a/src/strmap.h +++ b/src/strmap.h @@ -17,6 +17,7 @@ __KHASH_TYPE(str, const char *, void *); typedef khash_t(str) git_strmap; +typedef khiter_t git_strmap_iter; #define GIT__USE_STRMAP \ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) @@ -31,7 +32,9 @@ typedef khash_t(str) git_strmap; #define git_strmap_valid_index(h, idx) (idx != kh_end(h)) #define git_strmap_exists(h, k) (kh_get(str, h, k) != kh_end(h)) +#define git_strmap_has_data(h, idx) kh_exist(h, idx) +#define git_strmap_key(h, idx) kh_key(h, idx) #define git_strmap_value_at(h, idx) kh_val(h, idx) #define git_strmap_set_value_at(h, idx, v) kh_val(h, idx) = v #define git_strmap_delete_at(h, idx) kh_del(str, h, idx) @@ -61,4 +64,12 @@ typedef khash_t(str) git_strmap; #define git_strmap_foreach kh_foreach #define git_strmap_foreach_value kh_foreach_value +#define git_strmap_begin kh_begin +#define git_strmap_end kh_end + +int git_strmap_next( + void **data, + git_strmap_iter* iter, + git_strmap *map); + #endif diff --git a/src/submodule.c b/src/submodule.c index 89eba2aa4..586494fed 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -9,9 +9,7 @@ #include "git2/config.h" #include "git2/sys/config.h" #include "git2/types.h" -#include "git2/repository.h" #include "git2/index.h" -#include "git2/submodule.h" #include "buffer.h" #include "buf_text.h" #include "vector.h" @@ -32,6 +30,8 @@ static git_cvar_map _sm_update_map[] = { {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, }; static git_cvar_map _sm_ignore_map[] = { @@ -39,6 +39,8 @@ static git_cvar_map _sm_ignore_map[] = { {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, }; static kh_inline khint_t str_hash_no_trailing_slash(const char *s) @@ -73,15 +75,11 @@ static int load_submodule_config(git_repository *repo); static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *); static int lookup_head_remote(git_buf *url, git_repository *repo); static int submodule_get(git_submodule **, git_repository *, const char *, const char *); -static void submodule_release(git_submodule *sm, int decr); -static int submodule_load_from_index(git_repository *, const git_index_entry *); -static int submodule_load_from_head(git_repository*, const char*, const git_oid*); static int submodule_load_from_config(const git_config_entry *, void *); static int submodule_load_from_wd_lite(git_submodule *, const char *, void *); static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); -static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); -static int submodule_index_status(unsigned int *status, git_submodule *sm); -static int submodule_wd_status(unsigned int *status, git_submodule *sm); +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); static int submodule_cmp(const void *a, const void *b) { @@ -163,7 +161,7 @@ int git_submodule_foreach( * us from issuing a callback twice for a submodule where the name * and path are not the same. */ - if (sm->refcount > 1) { + if (GIT_REFCOUNT_VAL(sm) > 1) { if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND) continue; if ((error = git_vector_insert(&seen, sm)) < 0) @@ -195,9 +193,7 @@ void git_submodule_config_free(git_repository *repo) if (smcfg == NULL) return; - git_strmap_foreach_value(smcfg, sm, { - submodule_release(sm,1); - }); + git_strmap_foreach_value(smcfg, sm, { git_submodule_free(sm); }); git_strmap_free(smcfg); } @@ -338,7 +334,7 @@ int git_submodule_add_finalize(git_submodule *sm) assert(sm); - if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) return error; @@ -348,7 +344,7 @@ int git_submodule_add_finalize(git_submodule *sm) int git_submodule_add_to_index(git_submodule *sm, int write_index) { int error; - git_repository *repo, *sm_repo = NULL; + git_repository *sm_repo = NULL; git_index *index; git_buf path = GIT_BUF_INIT; git_commit *head; @@ -357,14 +353,12 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) assert(sm); - repo = sm->owner; - /* force reload of wd OID by git_submodule_open */ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; - if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || (error = git_buf_joinpath( - &path, git_repository_workdir(repo), sm->path)) < 0 || + &path, git_repository_workdir(sm->repo), sm->path)) < 0 || (error = git_submodule_open(&sm_repo, sm)) < 0) goto cleanup; @@ -378,7 +372,8 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) memset(&entry, 0, sizeof(entry)); entry.path = sm->path; - git_index_entry__init_from_stat(&entry, &st); + git_index_entry__init_from_stat( + &entry, &st, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE)); /* calling git_submodule_open will have set sm->wd_oid if possible */ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { @@ -416,15 +411,34 @@ cleanup: return error; } +const char *git_submodule_ignore_to_str(git_submodule_ignore_t ignore) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_ignore_map); ++i) + if (_sm_ignore_map[i].map_value == ignore) + return _sm_ignore_map[i].str_match; + return NULL; +} + +const char *git_submodule_update_to_str(git_submodule_update_t update) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) + if (_sm_update_map[i].map_value == update) + return _sm_update_map[i].str_match; + return NULL; +} + int git_submodule_save(git_submodule *submodule) { int error = 0; git_config_backend *mods; git_buf key = GIT_BUF_INIT; + const char *val; assert(submodule); - mods = open_gitmodules(submodule->owner, true, NULL); + mods = open_gitmodules(submodule->repo, true, NULL); if (!mods) { giterr_set(GITERR_SUBMODULE, "Adding submodules to a bare repository is not supported (for now)"); @@ -445,22 +459,14 @@ int git_submodule_save(git_submodule *submodule) goto cleanup; if (!(error = submodule_config_key_trunc_puts(&key, "update")) && - submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) - { - const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? - NULL : _sm_update_map[submodule->update].str_match; + (val = git_submodule_update_to_str(submodule->update)) != NULL) error = git_config_file_set_string(mods, key.ptr, val); - } if (error < 0) goto cleanup; if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) && - submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT) - { - const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ? - NULL : _sm_ignore_map[submodule->ignore].str_match; + (val = git_submodule_ignore_to_str(submodule->ignore)) != NULL) error = git_config_file_set_string(mods, key.ptr, val); - } if (error < 0) goto cleanup; @@ -487,7 +493,7 @@ cleanup: git_repository *git_submodule_owner(git_submodule *submodule) { assert(submodule); - return submodule->owner; + return submodule->repo; } const char *git_submodule_name(git_submodule *submodule) @@ -544,11 +550,12 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule) { assert(submodule); + /* load unless we think we have a valid oid */ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { git_repository *subrepo; /* calling submodule open grabs the HEAD OID if possible */ - if (!git_submodule_open(&subrepo, submodule)) + if (!git_submodule_open_bare(&subrepo, submodule)) git_repository_free(subrepo); else giterr_clear(); @@ -563,7 +570,8 @@ const git_oid *git_submodule_wd_id(git_submodule *submodule) git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) { assert(submodule); - return submodule->ignore; + return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? + GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; } git_submodule_ignore_t git_submodule_set_ignore( @@ -573,7 +581,7 @@ git_submodule_ignore_t git_submodule_set_ignore( assert(submodule); - if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) + if (ignore == GIT_SUBMODULE_IGNORE_RESET) ignore = submodule->ignore_default; old = submodule->ignore; @@ -584,7 +592,8 @@ git_submodule_ignore_t git_submodule_set_ignore( git_submodule_update_t git_submodule_update(git_submodule *submodule) { assert(submodule); - return submodule->update; + return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? + GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; } git_submodule_update_t git_submodule_set_update( @@ -594,7 +603,7 @@ git_submodule_update_t git_submodule_set_update( assert(submodule); - if (update == GIT_SUBMODULE_UPDATE_DEFAULT) + if (update == GIT_SUBMODULE_UPDATE_RESET) update = submodule->update_default; old = submodule->update; @@ -625,6 +634,7 @@ int git_submodule_set_fetch_recurse_submodules( int git_submodule_init(git_submodule *submodule, int overwrite) { int error; + const char *val; /* write "submodule.NAME.url" */ @@ -641,14 +651,10 @@ int git_submodule_init(git_submodule *submodule, int overwrite) /* write "submodule.NAME.update" if not default */ - if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) - error = submodule_update_config( - submodule, "update", NULL, (overwrite != 0), false); - else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) - error = submodule_update_config( - submodule, "update", - _sm_update_map[submodule->update].str_match, - (overwrite != 0), false); + val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : git_submodule_update_to_str(submodule->update); + error = submodule_update_config( + submodule, "update", val, (overwrite != 0), false); return error; } @@ -667,51 +673,70 @@ int git_submodule_sync(git_submodule *submodule) submodule, "url", submodule->url, true, true); } -int git_submodule_open( - git_repository **subrepo, - git_submodule *submodule) +static int git_submodule__open( + git_repository **subrepo, git_submodule *sm, bool bare) { int error; git_buf path = GIT_BUF_INIT; - git_repository *repo; - const char *workdir; + unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; + const char *wd; - assert(submodule && subrepo); + assert(sm && subrepo); - repo = submodule->owner; - workdir = git_repository_workdir(repo); + if (git_repository__ensure_not_bare( + sm->repo, "open submodule repository") < 0) + return GIT_EBAREREPO; - if (!workdir) { - giterr_set(GITERR_REPOSITORY, - "Cannot open submodule repository in a bare repo"); - return GIT_ENOTFOUND; - } + wd = git_repository_workdir(sm->repo); - if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) { - giterr_set(GITERR_REPOSITORY, - "Cannot open submodule repository that is not checked out"); - return GIT_ENOTFOUND; - } - - if (git_buf_joinpath(&path, workdir, submodule->path) < 0) + if (git_buf_joinpath(&path, wd, sm->path) < 0 || + git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0) return -1; - error = git_repository_open(subrepo, path.ptr); + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + + if (bare) + flags |= GIT_REPOSITORY_OPEN_BARE; + + error = git_repository_open_ext(subrepo, path.ptr, flags, wd); + + /* if we opened the submodule successfully, grab HEAD OID, etc. */ + if (!error) { + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + giterr_clear(); + } else if (git_path_exists(path.ptr)) { + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | + GIT_SUBMODULE_STATUS_IN_WD; + } else { + git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */ + + if (git_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + } git_buf_free(&path); - /* if we have opened the submodule successfully, let's grab the HEAD OID */ - if (!error) { - if (!git_reference_name_to_id( - &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) - submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; - else - giterr_clear(); - } - return error; } +int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, true); +} + +int git_submodule_open(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, false); +} + int git_submodule_reload_all(git_repository *repo) { assert(repo); @@ -719,74 +744,100 @@ int git_submodule_reload_all(git_repository *repo) return load_submodule_config(repo); } +static void submodule_update_from_index_entry( + git_submodule *sm, const git_index_entry *ie) +{ + bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; + + if (!S_ISGITLINK(ie->mode)) { + if (!already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else { + if (already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + else + git_oid_cpy(&sm->index_oid, &ie->oid); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID; + } +} + +static int submodule_update_index(git_submodule *sm) +{ + git_index *index; + const git_index_entry *ie; + + if (git_repository_index__weakptr(&index, sm->repo) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + if (!(ie = git_index_get_bypath(index, sm->path, 0))) + return 0; + + submodule_update_from_index_entry(sm, ie); + + return 0; +} + +static void submodule_update_from_head_data( + git_submodule *sm, mode_t mode, const git_oid *id) +{ + if (!S_ISGITLINK(mode)) + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + else { + git_oid_cpy(&sm->head_oid, id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID; + } +} + +static int submodule_update_head(git_submodule *submodule) +{ + git_tree *head = NULL; + git_tree_entry *te = NULL; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + /* if we can't look up file in current head, then done */ + if (git_repository_head_tree(&head, submodule->repo) < 0 || + git_tree_entry_bypath(&te, head, submodule->path) < 0) + giterr_clear(); + else + submodule_update_from_head_data(submodule, te->attr, &te->oid); + + git_tree_entry_free(te); + git_tree_free(head); + return 0; +} + int git_submodule_reload(git_submodule *submodule) { - git_repository *repo; - git_index *index; - int error; - size_t pos; - git_tree *head; + int error = 0; git_config_backend *mods; assert(submodule); /* refresh index data */ - repo = submodule->owner; - if (git_repository_index__weakptr(&index, repo) < 0) + if (submodule_update_index(submodule) < 0) return -1; - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID); - - if (!git_index_find(&pos, index, submodule->path)) { - const git_index_entry *entry = git_index_get_byindex(index, pos); - - if (S_ISGITLINK(entry->mode)) { - if ((error = submodule_load_from_index(repo, entry)) < 0) - return error; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); - } - } - /* refresh HEAD tree data */ - if (!(error = git_repository_head_tree(&head, repo))) { - git_tree_entry *te; - - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID); - - if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { - - if (S_ISGITLINK(te->attr)) { - error = submodule_load_from_head(repo, submodule->path, &te->oid); - } else { - submodule_mode_mismatch( - repo, submodule->path, - GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); - } - - git_tree_entry_free(te); - } - else if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = 0; - } - - git_tree_free(head); - } - - if (error < 0) - return error; + if (submodule_update_head(submodule) < 0) + return -1; /* refresh config data */ - if ((mods = open_gitmodules(repo, false, NULL)) != NULL) { + mods = open_gitmodules(submodule->repo, false, NULL); + if (mods != NULL) { git_buf path = GIT_BUF_INIT; git_buf_sets(&path, "submodule\\."); @@ -797,7 +848,7 @@ int git_submodule_reload(git_submodule *submodule) error = -1; else error = git_config_file_foreach_match( - mods, path.ptr, submodule_load_from_config, repo); + mods, path.ptr, submodule_load_from_config, submodule->repo); git_buf_free(&path); git_config_file_free(mods); @@ -816,40 +867,92 @@ int git_submodule_reload(git_submodule *submodule) return error; } -int git_submodule_status( - unsigned int *status, - git_submodule *submodule) +static void submodule_copy_oid_maybe( + git_oid *tgt, const git_oid *src, bool valid) { - int error = 0; - unsigned int status_val; - - assert(status && submodule); - - status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags); - - if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) { - if (!(error = submodule_index_status(&status_val, submodule))) - error = submodule_wd_status(&status_val, submodule); + if (tgt) { + if (valid) + memcpy(tgt, src, sizeof(*tgt)); + else + memset(tgt, 0, sizeof(*tgt)); } - - *status = status_val; - - return error; } -int git_submodule_location( - unsigned int *location_status, - git_submodule *submodule) +int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign) { - assert(location_status && submodule); + unsigned int status; + git_repository *smrepo = NULL; - *location_status = submodule->flags & - (GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD); + if (ign < GIT_SUBMODULE_IGNORE_NONE) + ign = sm->ignore; + + /* only return location info if ignore == all */ + if (ign == GIT_SUBMODULE_IGNORE_ALL) { + *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); + return 0; + } + + /* refresh the index OID */ + if (submodule_update_index(sm) < 0) + return -1; + + /* refresh the HEAD OID */ + if (submodule_update_head(sm) < 0) + return -1; + + /* for ignore == dirty, don't scan the working directory */ + if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { + /* git_submodule_open_bare will load WD OID data */ + if (git_submodule_open_bare(&smrepo, sm) < 0) + giterr_clear(); + else + git_repository_free(smrepo); + smrepo = NULL; + } else if (git_submodule_open(&smrepo, sm) < 0) { + giterr_clear(); + smrepo = NULL; + } + + status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); + + submodule_get_index_status(&status, sm); + submodule_get_wd_status(&status, sm, smrepo, ign); + + git_repository_free(smrepo); + + *out_status = status; + + submodule_copy_oid_maybe(out_head_id, &sm->head_oid, + (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); + submodule_copy_oid_maybe(out_index_id, &sm->index_oid, + (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); + submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); return 0; } +int git_submodule_status(unsigned int *status, git_submodule *sm) +{ + assert(status && sm); + + return git_submodule__status(status, NULL, NULL, NULL, sm, 0); +} + +int git_submodule_location(unsigned int *location, git_submodule *sm) +{ + assert(location && sm); + + return git_submodule__status( + location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); +} + /* * INTERNAL FUNCTIONS @@ -857,54 +960,50 @@ int git_submodule_location( static git_submodule *submodule_alloc(git_repository *repo, const char *name) { + size_t namelen; git_submodule *sm; - if (!name || !strlen(name)) { + if (!name || !(namelen = strlen(name))) { giterr_set(GITERR_SUBMODULE, "Invalid submodule name"); return NULL; } sm = git__calloc(1, sizeof(git_submodule)); if (sm == NULL) - goto fail; + return NULL; - sm->path = sm->name = git__strdup(name); - if (!sm->name) - goto fail; + sm->name = sm->path = git__strdup(name); + if (!sm->name) { + git__free(sm); + return NULL; + } - sm->owner = repo; - sm->refcount = 1; + GIT_REFCOUNT_INC(sm); + sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; + sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; + sm->repo = repo; return sm; - -fail: - submodule_release(sm, 0); - return NULL; } -static void submodule_release(git_submodule *sm, int decr) +static void submodule_release(git_submodule *sm) { if (!sm) return; - sm->refcount -= decr; + if (sm->path != sm->name) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__memzero(sm, sizeof(*sm)); + git__free(sm); +} - if (sm->refcount == 0) { - if (sm->name != sm->path) { - git__free(sm->path); - sm->path = NULL; - } - - git__free(sm->name); - sm->name = NULL; - - git__free(sm->url); - sm->url = NULL; - - sm->owner = NULL; - - git__free(sm); - } +void git_submodule_free(git_submodule *sm) +{ + if (!sm) + return; + GIT_REFCOUNT_DEC(sm, submodule_release); } static int submodule_get( @@ -927,6 +1026,7 @@ static int submodule_get( if (!git_strmap_valid_index(smcfg, pos)) { sm = submodule_alloc(repo, name); + GITERR_CHECK_ALLOC(sm); /* insert value at name - if another thread beats us to it, then use * their record and release our own. @@ -934,10 +1034,10 @@ static int submodule_get( pos = kh_put(str, smcfg, sm->name, &error); if (error < 0) { - submodule_release(sm, 1); + git_submodule_free(sm); sm = NULL; } else if (error == 0) { - submodule_release(sm, 1); + git_submodule_free(sm); sm = git_strmap_value_at(smcfg, pos); } else { git_strmap_set_value_at(smcfg, pos, sm); @@ -951,43 +1051,6 @@ static int submodule_get( return (sm != NULL) ? 0 : -1; } -static int submodule_load_from_index( - git_repository *repo, const git_index_entry *entry) -{ - git_submodule *sm; - - if (submodule_get(&sm, repo, entry->path, NULL) < 0) - return -1; - - if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) { - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; - return 0; - } - - sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX; - - git_oid_cpy(&sm->index_oid, &entry->oid); - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID; - - return 0; -} - -static int submodule_load_from_head( - git_repository *repo, const char *path, const git_oid *oid) -{ - git_submodule *sm; - - if (submodule_get(&sm, repo, path, NULL) < 0) - return -1; - - sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD; - - git_oid_cpy(&sm->head_oid, oid); - sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID; - - return 0; -} - static int submodule_config_error(const char *property, const char *value) { giterr_set(GITERR_INVALID, @@ -995,6 +1058,34 @@ static int submodule_config_error(const char *property, const char *value) return -1; } +int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { + *out = GIT_SUBMODULE_IGNORE_NONE; + return submodule_config_error("ignore", value); + } + + *out = (git_submodule_ignore_t)val; + return 0; +} + +int git_submodule_parse_update(git_submodule_update_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { + *out = GIT_SUBMODULE_UPDATE_CHECKOUT; + return submodule_config_error("update", value); + } + + *out = (git_submodule_update_t)val; + return 0; +} + static int submodule_load_from_config( const git_config_entry *entry, void *data) { @@ -1012,8 +1103,10 @@ static int submodule_load_from_config( namestart = key + strlen("submodule."); property = strrchr(namestart, '.'); - if (property == NULL) + + if (!property || (property == namestart)) return 0; + property++; is_path = (strcasecmp(property, "path") == 0); @@ -1047,11 +1140,11 @@ static int submodule_load_from_config( git_strmap_insert2(smcfg, alternate, sm, old_sm, error); if (error >= 0) - sm->refcount++; /* inserted under a new key */ + GIT_REFCOUNT_INC(sm); /* inserted under a new key */ /* if we replaced an old module under this key, release the old one */ if (old_sm && ((git_submodule *)old_sm) != sm) { - submodule_release(old_sm, 1); + git_submodule_free(old_sm); /* TODO: log warning about multiple submodules with same path */ } } @@ -1076,22 +1169,18 @@ static int submodule_load_from_config( return -1; } else if (strcasecmp(property, "update") == 0) { - int val; - if (git_config_lookup_map_value( - &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) - return submodule_config_error("update", value); - sm->update_default = sm->update = (git_submodule_update_t)val; + if (git_submodule_parse_update(&sm->update, value) < 0) + return -1; + sm->update_default = sm->update; } else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { if (git__parse_bool(&sm->fetch_recurse, value) < 0) return submodule_config_error("fetchRecurseSubmodules", value); } else if (strcasecmp(property, "ignore") == 0) { - int val; - if (git_config_lookup_map_value( - &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) - return submodule_config_error("ignore", value); - sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; + if (git_submodule_parse_ignore(&sm->ignore, value) < 0) + return -1; + sm->ignore_default = sm->ignore; } /* ignore other unknown submodule properties */ @@ -1101,13 +1190,15 @@ static int submodule_load_from_config( static int submodule_load_from_wd_lite( git_submodule *sm, const char *name, void *payload) { - git_repository *repo = git_submodule_owner(sm); git_buf path = GIT_BUF_INIT; GIT_UNUSED(name); GIT_UNUSED(payload); - if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0) + if (git_repository_is_bare(sm->repo)) + return 0; + + if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0) return -1; if (git_path_isdir(path.ptr)) @@ -1121,18 +1212,6 @@ static int submodule_load_from_wd_lite( return 0; } -static void submodule_mode_mismatch( - git_repository *repo, const char *path, unsigned int flag) -{ - khiter_t pos = git_strmap_lookup_index(repo->submodules, path); - - if (git_strmap_valid_index(repo->submodules, pos)) { - git_submodule *sm = git_strmap_value_at(repo->submodules, pos); - - sm->flags |= flag; - } -} - static int load_submodule_config_from_index( git_repository *repo, git_oid *gitmodules_oid) { @@ -1146,18 +1225,21 @@ static int load_submodule_config_from_index( return error; while (!(error = git_iterator_advance(&entry, i))) { + khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path); + git_submodule *sm; - if (S_ISGITLINK(entry->mode)) { - error = submodule_load_from_index(repo, entry); - if (error < 0) - break; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); + if (git_strmap_valid_index(repo->submodules, pos)) { + sm = git_strmap_value_at(repo->submodules, pos); - if (strcmp(entry->path, GIT_MODULES_FILE) == 0) - git_oid_cpy(gitmodules_oid, &entry->oid); - } + if (S_ISGITLINK(entry->mode)) + submodule_update_from_index_entry(sm, entry); + else + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + if (!submodule_get(&sm, repo, entry->path, NULL)) + submodule_update_from_index_entry(sm, entry); + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0) + git_oid_cpy(gitmodules_oid, &entry->oid); } if (error == GIT_ITEROVER) @@ -1176,8 +1258,11 @@ static int load_submodule_config_from_head( git_iterator *i; const git_index_entry *entry; - if ((error = git_repository_head_tree(&head, repo)) < 0) - return error; + /* if we can't look up current head, then there's no submodule in it */ + if (git_repository_head_tree(&head, repo) < 0) { + giterr_clear(); + return 0; + } if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) { git_tree_free(head); @@ -1185,18 +1270,24 @@ static int load_submodule_config_from_head( } while (!(error = git_iterator_advance(&entry, i))) { + khiter_t pos = git_strmap_lookup_index(repo->submodules, entry->path); + git_submodule *sm; - if (S_ISGITLINK(entry->mode)) { - error = submodule_load_from_head(repo, entry->path, &entry->oid); - if (error < 0) - break; - } else { - submodule_mode_mismatch( - repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); + if (git_strmap_valid_index(repo->submodules, pos)) { + sm = git_strmap_value_at(repo->submodules, pos); - if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && - git_oid_iszero(gitmodules_oid)) - git_oid_cpy(gitmodules_oid, &entry->oid); + if (S_ISGITLINK(entry->mode)) + submodule_update_from_head_data( + sm, entry->mode, &entry->oid); + else + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + if (!submodule_get(&sm, repo, entry->path, NULL)) + submodule_update_from_head_data( + sm, entry->mode, &entry->oid); + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && + git_oid_iszero(gitmodules_oid)) { + git_oid_cpy(gitmodules_oid, &entry->oid); } } @@ -1381,7 +1472,7 @@ static int submodule_update_config( assert(submodule); - error = git_repository_config__weakptr(&config, submodule->owner); + error = git_repository_config__weakptr(&config, submodule->repo); if (error < 0) return error; @@ -1409,11 +1500,13 @@ cleanup: return error; } -static int submodule_index_status(unsigned int *status, git_submodule *sm) +static void submodule_get_index_status(unsigned int *status, git_submodule *sm) { const git_oid *head_oid = git_submodule_head_id(sm); const git_oid *index_oid = git_submodule_index_id(sm); + *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; + if (!head_oid) { if (index_oid) *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; @@ -1422,27 +1515,23 @@ static int submodule_index_status(unsigned int *status, git_submodule *sm) *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; else if (!git_oid_equal(head_oid, index_oid)) *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; - - return 0; } -static int submodule_wd_status(unsigned int *status, git_submodule *sm) +static void submodule_get_wd_status( + unsigned int *status, + git_submodule *sm, + git_repository *sm_repo, + git_submodule_ignore_t ign) { - int error = 0; - const git_oid *wd_oid, *index_oid; - git_repository *sm_repo = NULL; + const git_oid *index_oid = git_submodule_index_id(sm); + const git_oid *wd_oid = + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; + git_tree *sm_head = NULL; + git_index *index = NULL; + git_diff_options opt = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; - /* open repo now if we need it (so wd_id() call won't reopen) */ - if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE || - sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) && - (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0) - { - if ((error = git_submodule_open(&sm_repo, sm)) < 0) - return error; - } - - index_oid = git_submodule_index_id(sm); - wd_oid = git_submodule_wd_id(sm); + *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; if (!index_oid) { if (wd_oid) @@ -1458,59 +1547,51 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) else if (!git_oid_equal(index_oid, wd_oid)) *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; - if (sm_repo != NULL) { - git_tree *sm_head; - git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + /* if we have no repo, then we're done */ + if (!sm_repo) + return; - /* the diffs below could be optimized with an early termination - * option to the git_diff functions, but for now this is sufficient - * (and certainly no worse that what core git does). - */ + /* the diffs below could be optimized with an early termination + * option to the git_diff functions, but for now this is sufficient + * (and certainly no worse that what core git does). + */ - /* perform head-to-index diff on submodule */ + if (ign == GIT_SUBMODULE_IGNORE_NONE) + opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0) - return error; + (void)git_repository_index__weakptr(&index, sm_repo); - if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE) - opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt); - - if (!error) { + /* if we don't have an unborn head, check diff with index */ + if (git_repository_head_tree(&sm_head, sm_repo) < 0) + giterr_clear(); + else { + /* perform head to index diff on submodule */ + if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) + giterr_clear(); + else { if (git_diff_num_deltas(diff) > 0) *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; } git_tree_free(sm_head); - - if (error < 0) - return error; - - /* perform index-to-workdir diff on submodule */ - - error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt); - - if (!error) { - size_t untracked = - git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); - - if (untracked > 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - - if (git_diff_num_deltas(diff) != untracked) - *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - - git_diff_list_free(diff); - diff = NULL; - } - - git_repository_free(sm_repo); } - return error; + /* perform index-to-workdir diff on submodule */ + if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) + giterr_clear(); + else { + size_t untracked = + git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); + + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; + + if (git_diff_num_deltas(diff) != untracked) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; + + git_diff_free(diff); + diff = NULL; + } } diff --git a/src/submodule.h b/src/submodule.h index ba8e2518e..b05937503 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -7,6 +7,10 @@ #ifndef INCLUDE_submodule_h__ #define INCLUDE_submodule_h__ +#include "git2/submodule.h" +#include "git2/repository.h" +#include "fileops.h" + /* Notes: * * Submodule information can be in four places: the index, the config files @@ -44,44 +48,51 @@ * an entry for every submodule found in the HEAD and index, and for every * submodule described in .gitmodules. The fields are as follows: * - * - `owner` is the git_repository containing this submodule + * - `rc` tracks the refcount of how many hash table entries in the + * git_submodule_cache there are for this submodule. It only comes into + * play if the name and path of the submodule differ. + * * - `name` is the name of the submodule from .gitmodules. * - `path` is the path to the submodule from the repo root. It is almost * always the same as `name`. * - `url` is the url for the submodule. - * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD. - * - `index_oid` is the SHA1 for the submodule recorded in the index. - * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule. * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `update_default` is the update value from the config * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `ignore_default` is the ignore value from the config * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. - * - `refcount` tracks how many hashmap entries there are for this submodule. - * It only comes into play if the name and path of the submodule differ. - * - `flags` is for internal use, tracking where this submodule has been - * found (head, index, config, workdir) and other misc info about it. + * + * - `repo` is the parent repository that contains this submodule. + * - `flags` after for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and known status info, etc. + * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. * * If the submodule has been added to .gitmodules but not yet git added, - * then the `index_oid` will be valid and zero. If the submodule has been - * deleted, but the delete has not been committed yet, then the `index_oid` - * will be set, but the `url` will be NULL. + * then the `index_oid` will be zero but still marked valid. If the + * submodule has been deleted, but the delete has not been committed yet, + * then the `index_oid` will be set, but the `url` will be NULL. */ struct git_submodule { - git_repository *owner; - char *name; - char *path; /* important: may point to same string data as "name" */ - char *url; - uint32_t flags; - git_oid head_oid; - git_oid index_oid; - git_oid wd_oid; + git_refcount rc; + /* information from config */ + char *name; + char *path; /* important: may just point to "name" string */ + char *url; git_submodule_update_t update; git_submodule_update_t update_default; git_submodule_ignore_t ignore; git_submodule_ignore_t ignore_default; int fetch_recurse; + /* internal information */ - int refcount; + git_repository *repo; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; }; /* Additional flags on top of public GIT_SUBMODULE_STATUS values */ @@ -99,4 +110,29 @@ enum { #define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ ((S) & ~(0xFFFFFFFFu << 20)) +/* Internal status fn returns status and optionally the various OIDs */ +extern int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign); + +/* Open submodule repository as bare repo for quick HEAD check, etc. */ +extern int git_submodule_open_bare( + git_repository **repo, + git_submodule *submodule); + +/* Release reference to submodule object - not currently for external use */ +extern void git_submodule_free(git_submodule *sm); + +extern int git_submodule_parse_ignore( + git_submodule_ignore_t *out, const char *value); +extern int git_submodule_parse_update( + git_submodule_update_t *out, const char *value); + +extern const char *git_submodule_ignore_to_str(git_submodule_ignore_t); +extern const char *git_submodule_update_to_str(git_submodule_update_t); + #endif diff --git a/src/tag.c b/src/tag.c index 71f4c1eb1..31a3c8b80 100644 --- a/src/tag.c +++ b/src/tag.c @@ -366,10 +366,10 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) return -1; - stream->write(stream, buffer, strlen(buffer)); + git_odb_stream_write(stream, buffer, strlen(buffer)); - error = stream->finalize_write(oid, stream); - stream->free(stream); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); if (error < 0) { git_buf_free(&ref_name); diff --git a/src/thread-utils.h b/src/thread-utils.h index 83148188d..914c1357d 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -38,15 +38,11 @@ typedef git_atomic git_atomic_ssize; #endif -GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) -{ - a->val = val; -} - #ifdef GIT_THREADS #define git_thread pthread_t -#define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg) +#define git_thread_create(thread, attr, start_routine, arg) \ + pthread_create(thread, attr, start_routine, arg) #define git_thread_kill(thread) pthread_cancel(thread) #define git_thread_exit(status) pthread_exit(status) #define git_thread_join(id, status) pthread_join(id, status) @@ -66,6 +62,41 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) #define git_cond_signal(c) pthread_cond_signal(c) #define git_cond_broadcast(c) pthread_cond_broadcast(c) +/* Pthread (-ish) rwlock + * + * This differs from normal pthreads rwlocks in two ways: + * 1. Separate APIs for releasing read locks and write locks (as + * opposed to the pure POSIX API which only has one unlock fn) + * 2. You should not use recursive read locks (i.e. grabbing a read + * lock in a thread that already holds a read lock) because the + * Windows implementation doesn't support it + */ +#define git_rwlock pthread_rwlock_t +#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) +#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_rdunlock(a) +#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_wrunlock(a) +#define git_rwlock_free(a) pthread_rwlock_destroy(a) +#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER + +#ifndef GIT_WIN32 +#define pthread_rwlock_rdunlock pthread_rwlock_unlock +#define pthread_rwlock_wrunlock pthread_rwlock_unlock +#endif + + +GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) +{ +#if defined(GIT_WIN32) + InterlockedExchange(&a->val, (LONG)val); +#elif defined(__GNUC__) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + GIT_INLINE(int) git_atomic_inc(git_atomic *a) { #if defined(GIT_WIN32) @@ -100,11 +131,11 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a) } GIT_INLINE(void *) git___compare_and_swap( - volatile void **ptr, void *oldval, void *newval) + void * volatile *ptr, void *oldval, void *newval) { volatile void *foundval; #if defined(GIT_WIN32) - foundval = InterlockedCompareExchangePointer(ptr, newval, oldval); + foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); #elif defined(__GNUC__) foundval = __sync_val_compare_and_swap(ptr, oldval, newval); #else @@ -113,6 +144,16 @@ GIT_INLINE(void *) git___compare_and_swap( return (foundval == oldval) ? oldval : newval; } +GIT_INLINE(volatile void *) git___swap( + void * volatile *ptr, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedExchangePointer(ptr, newval); +#else + return __sync_lock_test_and_set(ptr, newval); +#endif +} + #ifdef GIT_ARCH_64 GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) @@ -131,7 +172,7 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #else #define git_thread unsigned int -#define git_thread_create(thread, attr, start_routine, arg) (void)0 +#define git_thread_create(thread, attr, start_routine, arg) 0 #define git_thread_kill(thread) (void)0 #define git_thread_exit(status) (void)0 #define git_thread_join(id, status) (void)0 @@ -151,6 +192,22 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #define git_cond_signal(c) (void)0 #define git_cond_broadcast(c) (void)0 +/* Pthreads rwlock */ +#define git_rwlock unsigned int +#define git_rwlock_init(a) 0 +#define git_rwlock_rdlock(a) 0 +#define git_rwlock_rdunlock(a) (void)0 +#define git_rwlock_wrlock(a) 0 +#define git_rwlock_wrunlock(a) (void)0 +#define git_rwlock_free(a) (void)0 +#define GIT_RWLOCK_STATIC_INIT 0 + + +GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) +{ + a->val = val; +} + GIT_INLINE(int) git_atomic_inc(git_atomic *a) { return ++a->val; @@ -168,7 +225,7 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a) } GIT_INLINE(void *) git___compare_and_swap( - volatile void **ptr, void *oldval, void *newval) + void * volatile *ptr, void *oldval, void *newval) { if (*ptr == oldval) *ptr = newval; @@ -177,6 +234,14 @@ GIT_INLINE(void *) git___compare_and_swap( return oldval; } +GIT_INLINE(volatile void *) git___swap( + void * volatile *ptr, void *newval) +{ + volatile void *old = *ptr; + *ptr = newval; + return old; +} + #ifdef GIT_ARCH_64 GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) @@ -189,13 +254,18 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #endif +GIT_INLINE(int) git_atomic_get(git_atomic *a) +{ + return (int)a->val; +} + /* Atomically replace oldval with newval * @return oldval if it was replaced or newval if it was not */ #define git__compare_and_swap(P,O,N) \ - git___compare_and_swap((volatile void **)P, O, N) + git___compare_and_swap((void * volatile *)P, O, N) -#define git__swap(ptr, val) git__compare_and_swap(&ptr, ptr, val) +#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val) extern int git_online_cpus(void); diff --git a/src/transport.c b/src/transport.c index 37c244c97..ff926b1be 100644 --- a/src/transport.c +++ b/src/transport.c @@ -42,6 +42,8 @@ static transport_definition transports[] = { {NULL, 0, 0} }; +static git_vector additional_transports = GIT_VECTOR_INIT; + #define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 static int transport_find_fn(const char *url, git_transport_cb *callback, void **param) @@ -61,6 +63,14 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * definition = definition_iter; } + git_vector_foreach(&additional_transports, i, definition_iter) { + if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix))) + continue; + + if (definition_iter->priority > priority) + definition = definition_iter; + } + #ifdef GIT_WIN32 /* On Windows, it might not be possible to discern between absolute local * and ssh paths - first check if this is a valid local path that points @@ -73,7 +83,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * /* It could be a SSH remote path. Check to see if there's a : * SSH is an unsupported transport mechanism in this version of libgit2 */ if (!definition && strrchr(url, ':')) - definition = &dummy_transport_definition; + definition = &dummy_transport_definition; #else /* For other systems, perform the SSH check first, to avoid going to the * filesystem if it is not necessary */ @@ -97,7 +107,7 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void * *callback = definition->fn; *param = definition->param; - + return 0; } @@ -135,6 +145,62 @@ int git_transport_new(git_transport **out, git_remote *owner, const char *url) return 0; } +int git_transport_register( + const char *prefix, + unsigned priority, + git_transport_cb cb, + void *param) +{ + transport_definition *d; + + d = git__calloc(sizeof(transport_definition), 1); + GITERR_CHECK_ALLOC(d); + + d->prefix = git__strdup(prefix); + + if (!d->prefix) + goto on_error; + + d->priority = priority; + d->fn = cb; + d->param = param; + + if (git_vector_insert(&additional_transports, d) < 0) + goto on_error; + + return 0; + +on_error: + git__free(d->prefix); + git__free(d); + return -1; +} + +int git_transport_unregister( + const char *prefix, + unsigned priority) +{ + transport_definition *d; + unsigned i; + + git_vector_foreach(&additional_transports, i, d) { + if (d->priority == priority && !strcasecmp(d->prefix, prefix)) { + if (git_vector_remove(&additional_transports, i) < 0) + return -1; + + git__free(d->prefix); + git__free(d); + + if (!additional_transports.length) + git_vector_free(&additional_transports); + + return 0; + } + } + + return GIT_ENOTFOUND; +} + /* from remote.h */ int git_remote_valid_url(const char *url) { diff --git a/src/transports/cred.c b/src/transports/cred.c index ba5de6e93..05d2c8dc6 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -9,19 +9,49 @@ #include "smart.h" #include "git2/cred_helpers.h" +int git_cred_has_username(git_cred *cred) +{ + int ret = 0; + + switch (cred->credtype) { + case GIT_CREDTYPE_USERPASS_PLAINTEXT: { + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + ret = !!c->username; + break; + } + case GIT_CREDTYPE_SSH_KEY: { + git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + ret = !!c->username; + break; + } + case GIT_CREDTYPE_SSH_CUSTOM: { + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; + ret = !!c->username; + break; + } + case GIT_CREDTYPE_DEFAULT: { + ret = 0; + break; + } + } + + return ret; +} + static void plaintext_free(struct git_cred *cred) { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - size_t pass_len = strlen(c->password); git__free(c->username); /* Zero the memory which previously held the password */ - git__memzero(c->password, pass_len); - git__free(c->password); - - memset(c, 0, sizeof(*c)); + if (c->password) { + size_t pass_len = strlen(c->password); + git__memzero(c->password, pass_len); + git__free(c->password); + } + git__memzero(c, sizeof(*c)); git__free(c); } @@ -32,8 +62,7 @@ int git_cred_userpass_plaintext_new( { git_cred_userpass_plaintext *c; - if (!cred) - return -1; + assert(cred && username && password); c = git__malloc(sizeof(git_cred_userpass_plaintext)); GITERR_CHECK_ALLOC(c); @@ -59,53 +88,74 @@ int git_cred_userpass_plaintext_new( return 0; } -#ifdef GIT_SSH -static void ssh_keyfile_passphrase_free(struct git_cred *cred) +static void ssh_key_free(struct git_cred *cred) { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; - size_t pass_len = strlen(c->passphrase); + git_cred_ssh_key *c = + (git_cred_ssh_key *)cred; - if (c->publickey) { - git__free(c->publickey); - } - + git__free(c->username); + git__free(c->publickey); git__free(c->privatekey); - if (c->passphrase) { - /* Zero the memory which previously held the passphrase */ - git__memzero(c->passphrase, pass_len); - git__free(c->passphrase); - } + if (c->passphrase) { + /* Zero the memory which previously held the passphrase */ + size_t pass_len = strlen(c->passphrase); + git__memzero(c->passphrase, pass_len); + git__free(c->passphrase); + } - memset(c, 0, sizeof(*c)); + git__memzero(c, sizeof(*c)); + git__free(c); +} + +static void ssh_custom_free(struct git_cred *cred) +{ + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; + + git__free(c->username); + git__free(c->publickey); + + git__memzero(c, sizeof(*c)); + git__free(c); +} + +static void default_free(struct git_cred *cred) +{ + git_cred_default *c = (git_cred_default *)cred; git__free(c); } -int git_cred_ssh_keyfile_passphrase_new( +int git_cred_ssh_key_new( git_cred **cred, + const char *username, const char *publickey, const char *privatekey, const char *passphrase) { - git_cred_ssh_keyfile_passphrase *c; + git_cred_ssh_key *c; assert(cred && privatekey); - c = git__calloc(1, sizeof(git_cred_ssh_keyfile_passphrase)); + c = git__calloc(1, sizeof(git_cred_ssh_key)); GITERR_CHECK_ALLOC(c); - c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE; - c->parent.free = ssh_keyfile_passphrase_free; - - c->privatekey = git__strdup(privatekey); + c->parent.credtype = GIT_CREDTYPE_SSH_KEY; + c->parent.free = ssh_key_free; + + if (username) { + c->username = git__strdup(username); + GITERR_CHECK_ALLOC(c->username); + } + + c->privatekey = git__strdup(privatekey); GITERR_CHECK_ALLOC(c->privatekey); - - if (publickey) { + + if (publickey) { c->publickey = git__strdup(publickey); GITERR_CHECK_ALLOC(c->publickey); } - + if (passphrase) { c->passphrase = git__strdup(passphrase); GITERR_CHECK_ALLOC(c->passphrase); @@ -115,48 +165,56 @@ int git_cred_ssh_keyfile_passphrase_new( return 0; } -static void ssh_publickey_free(struct git_cred *cred) -{ - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; - - git__free(c->publickey); - - c->sign_callback = NULL; - c->sign_data = NULL; - - memset(c, 0, sizeof(*c)); - - git__free(c); -} - -int git_cred_ssh_publickey_new( +int git_cred_ssh_custom_new( git_cred **cred, + const char *username, const char *publickey, - size_t publickey_len, - LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)), - void *sign_data) + size_t publickey_len, + git_cred_sign_callback sign_callback, + void *sign_data) { - git_cred_ssh_publickey *c; + git_cred_ssh_custom *c; - if (!cred) - return -1; + assert(cred); - c = git__malloc(sizeof(git_cred_ssh_publickey)); + c = git__calloc(1, sizeof(git_cred_ssh_custom)); GITERR_CHECK_ALLOC(c); - c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY; - c->parent.free = ssh_publickey_free; - - c->publickey = git__malloc(publickey_len); - GITERR_CHECK_ALLOC(c->publickey); - - memcpy(c->publickey, publickey, publickey_len); - - c->publickey_len = publickey_len; - c->sign_callback = sign_callback; - c->sign_data = sign_data; + c->parent.credtype = GIT_CREDTYPE_SSH_CUSTOM; + c->parent.free = ssh_custom_free; + + if (username) { + c->username = git__strdup(username); + GITERR_CHECK_ALLOC(c->username); + } + + if (publickey_len > 0) { + c->publickey = git__malloc(publickey_len); + GITERR_CHECK_ALLOC(c->publickey); + + memcpy(c->publickey, publickey, publickey_len); + } + + c->publickey_len = publickey_len; + c->sign_callback = sign_callback; + c->sign_data = sign_data; *cred = &c->parent; return 0; } -#endif + +int git_cred_default_new(git_cred **cred) +{ + git_cred_default *c; + + assert(cred); + + c = git__calloc(1, sizeof(git_cred_default)); + GITERR_CHECK_ALLOC(c); + + c->credtype = GIT_CREDTYPE_DEFAULT; + c->free = default_free; + + *cred = c; + return 0; +} diff --git a/src/transports/git.c b/src/transports/git.c index 3a0b86345..5dcd4eff7 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -179,39 +179,33 @@ static int _git_uploadpack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host, *port, *user=NULL, *pass=NULL; + char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + const char *stream_url = url; git_stream *s; + int error = -1; *stream = NULL; - if (!git__prefixcmp(url, prefix_git)) - url += strlen(prefix_git); + stream_url += strlen(prefix_git); - if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0) + if (git_stream_alloc(t, stream_url, cmd_uploadpack, stream) < 0) return -1; s = (git_stream *)*stream; - if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0) - goto on_error; + if (!(error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) { + if (!(error = gitno_connect(&s->socket, host, port, 0))) + t->current_stream = s; - if (gitno_connect(&s->socket, host, port, 0) < 0) - goto on_error; - - t->current_stream = s; - git__free(host); - git__free(port); - git__free(user); - git__free(pass); - return 0; - -on_error: - if (*stream) + git__free(host); + git__free(port); + git__free(path); + git__free(user); + git__free(pass); + } else if (*stream) git_stream_free(*stream); - git__free(host); - git__free(port); - return -1; + return error; } static int _git_uploadpack( @@ -235,39 +229,33 @@ static int _git_receivepack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host, *port, *user=NULL, *pass=NULL; + char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + const char *stream_url = url; git_stream *s; + int error; *stream = NULL; - if (!git__prefixcmp(url, prefix_git)) - url += strlen(prefix_git); + stream_url += strlen(prefix_git); - if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0) + if (git_stream_alloc(t, stream_url, cmd_receivepack, stream) < 0) return -1; s = (git_stream *)*stream; - if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0) - goto on_error; + if (!(error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) { + if (!(error = gitno_connect(&s->socket, host, port, 0))) + t->current_stream = s; - if (gitno_connect(&s->socket, host, port, 0) < 0) - goto on_error; - - t->current_stream = s; - git__free(host); - git__free(port); - git__free(user); - git__free(pass); - return 0; - -on_error: - if (*stream) + git__free(host); + git__free(port); + git__free(path); + git__free(user); + git__free(pass); + } else if (*stream) git_stream_free(*stream); - git__free(host); - git__free(port); - return -1; + return error; } static int _git_receivepack( diff --git a/src/transports/http.c b/src/transports/http.c index eca06ead2..ace0d97d0 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -12,8 +12,6 @@ #include "netops.h" #include "smart.h" -static const char *prefix_http = "http://"; -static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; static const char *upload_pack_service_url = "/git-upload-pack"; @@ -59,16 +57,11 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; gitno_socket socket; - const char *path; - char *host; - char *port; - char *user_from_url; - char *pass_from_url; + gitno_connection_data connection_data; git_cred *cred; git_cred *url_cred; http_authmechanism_t auth_mechanism; - unsigned connected : 1, - use_ssl : 1; + bool connected; /* Parser structures */ http_parser parser; @@ -125,18 +118,12 @@ static int gen_request( size_t content_length) { http_subtransport *t = OWNING_SUBTRANSPORT(s); + const char *path = t->connection_data.path ? t->connection_data.path : "/"; - if (!t->path) - t->path = "/"; - - /* If we were redirected, make sure to respect that here */ - if (s->redirect_url) - git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url); - else - git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url); + 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, "Host: %s\r\n", t->host); + git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host); if (s->chunked || content_length > 0) { git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service); @@ -156,9 +143,9 @@ static int gen_request( return -1; /* Use url-parsed basic auth if username and password are both provided */ - if (!t->cred && t->user_from_url && t->pass_from_url) { - if (!t->url_cred && - git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0) + if (!t->cred && t->connection_data.user && t->connection_data.pass) { + if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred, + t->connection_data.user, t->connection_data.pass) < 0) return -1; if (apply_basic_credential(buf, t->url_cred) < 0) return -1; } @@ -209,7 +196,7 @@ static int on_header_ready(http_subtransport *t) } else if (!strcasecmp("Location", git_buf_cstr(name))) { if (!t->location) { - t->location= git__strdup(git_buf_cstr(value)); + t->location = git__strdup(git_buf_cstr(value)); GITERR_CHECK_ALLOC(t->location); } } @@ -283,7 +270,7 @@ static int on_headers_complete(http_parser *parser) if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, - t->user_from_url, + t->connection_data.user, allowed_types, t->owner->cred_acquire_payload) < 0) return PARSE_ERROR_GENERIC; @@ -298,20 +285,18 @@ static int on_headers_complete(http_parser *parser) /* Check for a redirect. * Right now we only permit a redirect to the same hostname. */ if ((parser->status_code == 301 || - parser->status_code == 302 || - (parser->status_code == 303 && get_verb == s->verb) || - parser->status_code == 307) && - t->location) { + parser->status_code == 302 || + (parser->status_code == 303 && get_verb == s->verb) || + parser->status_code == 307) && + t->location) { if (s->redirect_count >= 7) { giterr_set(GITERR_NET, "Too many redirects"); return t->parse_error = PARSE_ERROR_GENERIC; } - if (t->location[0] != '/') { - giterr_set(GITERR_NET, "Only relative redirects are supported"); + if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0) return t->parse_error = PARSE_ERROR_GENERIC; - } /* Set the redirect URL on the stream. This is a transfer of * ownership of the memory. */ @@ -468,7 +453,7 @@ static int http_connect(http_subtransport *t) if (t->socket.socket) gitno_close(&t->socket); - if (t->use_ssl) { + if (t->connection_data.use_ssl) { int tflags; if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0) @@ -480,7 +465,7 @@ static int http_connect(http_subtransport *t) flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; } - if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) + if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0) return -1; t->connected = 1; @@ -822,50 +807,30 @@ static int http_action( git_smart_service_t action) { http_subtransport *t = (http_subtransport *)subtransport; - const char *default_port = NULL; int ret; if (!stream) return -1; - if (!t->host || !t->port || !t->path) { - if (!git__prefixcmp(url, prefix_http)) { - url = url + strlen(prefix_http); - default_port = "80"; - } - - if (!git__prefixcmp(url, prefix_https)) { - url += strlen(prefix_https); - default_port = "443"; - t->use_ssl = 1; - } - - if (!default_port) - return -1; - - if ((ret = gitno_extract_url_parts(&t->host, &t->port, - &t->user_from_url, &t->pass_from_url, url, default_port)) < 0) - return ret; - - t->path = strchr(url, '/'); - } + if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) && + (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0) + return ret; if (http_connect(t) < 0) return -1; - switch (action) - { - case GIT_SERVICE_UPLOADPACK_LS: - return http_uploadpack_ls(t, stream); + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return http_uploadpack_ls(t, stream); - case GIT_SERVICE_UPLOADPACK: - return http_uploadpack(t, stream); + case GIT_SERVICE_UPLOADPACK: + return http_uploadpack(t, stream); - case GIT_SERVICE_RECEIVEPACK_LS: - return http_receivepack_ls(t, stream); + case GIT_SERVICE_RECEIVEPACK_LS: + return http_receivepack_ls(t, stream); - case GIT_SERVICE_RECEIVEPACK: - return http_receivepack(t, stream); + case GIT_SERVICE_RECEIVEPACK: + return http_receivepack(t, stream); } *stream = NULL; @@ -893,25 +858,7 @@ static int http_close(git_smart_subtransport *subtransport) t->url_cred = NULL; } - if (t->host) { - git__free(t->host); - t->host = NULL; - } - - if (t->port) { - git__free(t->port); - t->port = NULL; - } - - if (t->user_from_url) { - git__free(t->user_from_url); - t->user_from_url = NULL; - } - - if (t->pass_from_url) { - git__free(t->pass_from_url); - t->pass_from_url = NULL; - } + gitno_connection_data_free_ptrs(&t->connection_data); return 0; } diff --git a/src/transports/local.c b/src/transports/local.c index 550060958..4502f0202 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -119,15 +119,24 @@ on_error: static int store_refs(transport_local *t) { - unsigned int i; + size_t i; + git_remote_head *head; git_strarray ref_names = {0}; assert(t); - if (git_reference_list(&ref_names, t->repo) < 0 || - git_vector_init(&t->refs, ref_names.count, NULL) < 0) + if (git_reference_list(&ref_names, t->repo) < 0) goto on_error; + /* Clear all heads we might have fetched in a previous connect */ + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + /* Clear the vector so we can reuse it */ + git_vector_clear(&t->refs); + /* Sort the references first */ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); @@ -204,21 +213,17 @@ static int local_connect( return 0; } -static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) { transport_local *t = (transport_local *)transport; - unsigned int i; - git_remote_head *head = NULL; if (!t->have_refs) { giterr_set(GITERR_NET, "The transport has not yet loaded the refs"); return -1; } - git_vector_foreach(&t->refs, i, head) { - if (list_cb(head, payload)) - return GIT_EUSER; - } + *out = (const git_remote_head **) t->refs.contents; + *size = t->refs.length; return 0; } @@ -278,9 +283,9 @@ static int local_push_copy_object( odb_obj_size, odb_obj_type)) < 0) goto on_error; - if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj), + if (git_odb_stream_write(odb_stream, (char *)git_odb_object_data(odb_obj), odb_obj_size) < 0 || - odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) { + git_odb_stream_finalize_write(&remote_odb_obj_oid, odb_stream) < 0) { error = -1; } else if (git_oid__cmp(&obj->id, &remote_odb_obj_oid) != 0) { giterr_set(GITERR_ODB, "Error when writing object to remote odb " @@ -289,7 +294,7 @@ static int local_push_copy_object( error = -1; } - odb_stream->free(odb_stream); + git_odb_stream_free(odb_stream); on_error: git_odb_object_free(odb_obj); @@ -352,7 +357,8 @@ static int local_push( non-bare repo push support would require checking configs to see if we should override the default 'don't let this happen' behavior */ if (!remote_repo->is_bare) { - error = -1; + error = GIT_EBAREREPO; + giterr_set(GITERR_INVALID, "Local push doesn't (yet) support pushing to non-bare repos."); goto on_error; } @@ -424,7 +430,7 @@ static int local_push( if (!url || t->parent.close(&t->parent) < 0 || t->parent.connect(&t->parent, url, - push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags)) + push->remote->callbacks.credentials, NULL, GIT_DIRECTION_PUSH, flags)) goto on_error; } @@ -449,7 +455,7 @@ static int foreach_cb(void *buf, size_t len, void *payload) foreach_data *data = (foreach_data*)payload; data->stats->received_bytes += len; - return data->writepack->add(data->writepack, buf, len, data->stats); + return data->writepack->append(data->writepack, buf, len, data->stats); } static int local_download_pack( @@ -571,8 +577,6 @@ static void local_cancel(git_transport *transport) static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; - size_t i; - git_remote_head *head; t->connected = 0; @@ -586,19 +590,21 @@ static int local_close(git_transport *transport) t->url = NULL; } - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } - - git_vector_free(&t->refs); - return 0; } static void local_free(git_transport *transport) { transport_local *t = (transport_local *)transport; + size_t i; + git_remote_head *head; + + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + git_vector_free(&t->refs); /* Close the transport, if it's still open. */ local_close(transport); @@ -632,6 +638,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) t->parent.read_flags = local_read_flags; t->parent.cancel = local_cancel; + git_vector_init(&t->refs, 0, NULL); t->owner = owner; *out = (git_transport *) t; diff --git a/src/transports/smart.c b/src/transports/smart.c index 416eb221f..5242beb65 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -23,8 +23,13 @@ static int git_smart__recv_cb(gitno_buffer *buf) buf->offset += bytes_read; - if (t->packetsize_cb) - t->packetsize_cb(bytes_read, t->packetsize_payload); + if (t->packetsize_cb && !t->cancelled.val) + if (t->packetsize_cb(bytes_read, t->packetsize_payload)) { + git_atomic_set(&t->cancelled, 1); + + giterr_clear(); + return GIT_EUSER; + } return (int)(buf->offset - old_len); } @@ -58,6 +63,24 @@ static int git_smart__set_callbacks( return 0; } +int git_smart__update_heads(transport_smart *t) +{ + size_t i; + git_pkt *pkt; + + git_vector_clear(&t->heads); + git_vector_foreach(&t->refs, i, pkt) { + git_pkt_ref *ref = (git_pkt_ref *) pkt; + if (pkt->type != GIT_PKT_REF) + continue; + + if (git_vector_insert(&t->heads, &ref->head) < 0) + return -1; + } + + return 0; +} + static int git_smart__connect( git_transport *transport, const char *url, @@ -135,6 +158,9 @@ static int git_smart__connect( git_pkt_free((git_pkt *)first); } + /* Keep a list of heads for _ls */ + git_smart__update_heads(t); + if (t->rpc && git_smart__reset_stream(t, false) < 0) return -1; @@ -144,28 +170,17 @@ static int git_smart__connect( return 0; } -static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport) { transport_smart *t = (transport_smart *)transport; - unsigned int i; - git_pkt *p = NULL; if (!t->have_refs) { giterr_set(GITERR_NET, "The transport has not yet loaded the refs"); return -1; } - git_vector_foreach(&t->refs, i, p) { - git_pkt_ref *pkt = NULL; - - if (p->type != GIT_PKT_REF) - continue; - - pkt = (git_pkt_ref *)p; - - if (list_cb(&pkt->head, payload)) - return GIT_EUSER; - } + *out = (const git_remote_head **) t->heads.contents; + *size = t->heads.length; return 0; } @@ -288,6 +303,7 @@ static void git_smart__free(git_transport *transport) /* Free the subtransport */ t->wrapped->free(t->wrapped); + git_vector_free(&t->heads); git_vector_foreach(refs, i, p) git_pkt_free(p); @@ -335,6 +351,11 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) return -1; } + if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) { + git__free(t); + return -1; + } + if (definition->callback(&t->wrapped, &t->parent) < 0) { git__free(t); return -1; diff --git a/src/transports/smart.h b/src/transports/smart.h index c52401a3c..32f0be7f2 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -16,11 +16,13 @@ #define GIT_CAP_OFS_DELTA "ofs-delta" #define GIT_CAP_MULTI_ACK "multi_ack" +#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed" #define GIT_CAP_SIDE_BAND "side-band" #define GIT_CAP_SIDE_BAND_64K "side-band-64k" #define GIT_CAP_INCLUDE_TAG "include-tag" #define GIT_CAP_DELETE_REFS "delete-refs" #define GIT_CAP_REPORT_STATUS "report-status" +#define GIT_CAP_THIN_PACK "thin-pack" enum git_pkt_type { GIT_PKT_CMD, @@ -39,7 +41,7 @@ enum git_pkt_type { GIT_PKT_UNPACK, }; -/* Used for multi-ack */ +/* Used for multi_ack and mutli_ack_detailed */ enum git_ack_status { GIT_ACK_NONE, GIT_ACK_CONTINUE, @@ -112,14 +114,16 @@ typedef struct transport_smart_caps { int common:1, ofs_delta:1, multi_ack: 1, + multi_ack_detailed: 1, side_band:1, side_band_64k:1, include_tag:1, delete_refs:1, - report_status:1; + report_status:1, + thin_pack:1; } transport_smart_caps; -typedef void (*packetsize_cb)(size_t received, void *payload); +typedef int (*packetsize_cb)(size_t received, void *payload); typedef struct { git_transport parent; @@ -136,6 +140,7 @@ typedef struct { git_smart_subtransport_stream *current_stream; transport_smart_caps caps; git_vector refs; + git_vector heads; git_vector common; git_atomic cancelled; packetsize_cb packetsize_cb; @@ -169,6 +174,8 @@ int git_smart__download_pack( int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); +int git_smart__update_heads(transport_smart *t); + /* smart_pkt.c */ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); int git_pkt_buffer_flush(git_buf *buf); diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 99da37567..2bb09c750 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -39,7 +39,7 @@ static int flush_pkt(git_pkt **out) return 0; } -/* the rest of the line will be useful for multi_ack */ +/* the rest of the line will be useful for multi_ack and multi_ack_detailed */ static int ack_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_ack *pkt; @@ -62,6 +62,10 @@ static int ack_pkt(git_pkt **out, const char *line, size_t len) if (len >= 7) { if (!git__prefixcmp(line + 1, "continue")) pkt->status = GIT_ACK_CONTINUE; + if (!git__prefixcmp(line + 1, "common")) + pkt->status = GIT_ACK_COMMON; + if (!git__prefixcmp(line + 1, "ready")) + pkt->status = GIT_ACK_READY; } *out = (git_pkt *) pkt; @@ -456,22 +460,27 @@ static int buffer_want_with_caps(const git_remote_head *head, transport_smart_ca char oid[GIT_OID_HEXSZ +1] = {0}; unsigned int len; - /* Prefer side-band-64k if the server supports both */ - if (caps->side_band) { - if (caps->side_band_64k) - git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); - else - git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND); - } - if (caps->ofs_delta) - git_buf_puts(&str, GIT_CAP_OFS_DELTA " "); - - if (caps->multi_ack) + /* Prefer multi_ack_detailed */ + if (caps->multi_ack_detailed) + git_buf_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " "); + else if (caps->multi_ack) git_buf_puts(&str, GIT_CAP_MULTI_ACK " "); + /* Prefer side-band-64k if the server supports both */ + if (caps->side_band_64k) + git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); + else if (caps->side_band) + git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND); + if (caps->include_tag) git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " "); + if (caps->thin_pack) + git_buf_puts(&str, GIT_CAP_THIN_PACK " "); + + if (caps->ofs_delta) + git_buf_puts(&str, GIT_CAP_OFS_DELTA " "); + if (git_buf_oom(&str)) return -1; diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 636616717..3bf1f9329 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -13,8 +13,11 @@ #include "push.h" #include "pack-objects.h" #include "remote.h" +#include "util.h" #define NETWORK_XFER_THRESHOLD (100*1024) +/* The minimal interval between progress updates (in seconds). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 int git_smart__store_refs(transport_smart *t, int flushes) { @@ -46,7 +49,7 @@ int git_smart__store_refs(transport_smart *t, int flushes) if (error == GIT_EBUFS) { if ((recvd = gitno_recv(buf)) < 0) - return -1; + return recvd; if (recvd == 0 && !flush) { giterr_set(GITERR_NET, "Early EOF"); @@ -94,6 +97,13 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + /* Keep multi_ack_detailed before multi_ack */ + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) { + caps->common = caps->multi_ack_detailed = 1; + ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED); + continue; + } + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { caps->common = caps->multi_ack = 1; ptr += strlen(GIT_CAP_MULTI_ACK); @@ -125,6 +135,12 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { + caps->common = caps->thin_pack = 1; + ptr += strlen(GIT_CAP_THIN_PACK); + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -148,10 +164,10 @@ static int recv_pkt(git_pkt **out, gitno_buffer *buf) break; /* return the pkt */ if (error < 0 && error != GIT_EBUFS) - return -1; + return error; if ((ret = gitno_recv(buf)) < 0) - return -1; + return ret; } while (error); gitno_consume(buf, line_end); @@ -168,10 +184,11 @@ static int store_common(transport_smart *t) { git_pkt *pkt = NULL; gitno_buffer *buf = &t->buffer; + int error; do { - if (recv_pkt(&pkt, buf) < 0) - return -1; + if ((error = recv_pkt(&pkt, buf)) < 0) + return error; if (pkt->type == GIT_PKT_ACK) { if (git_vector_insert(&t->common, pkt) < 0) @@ -211,6 +228,7 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo) if (git_reference_type(ref) == GIT_REF_SYMBOLIC) continue; + if (git_revwalk_push(walk, git_reference_target(ref)) < 0) goto on_error; @@ -227,7 +245,33 @@ on_error: return -1; } -int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count) +static int wait_while_ack(gitno_buffer *buf) +{ + int error; + git_pkt_ack *pkt = NULL; + + while (1) { + git__free(pkt); + + if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0) + return error; + + if (pkt->type == GIT_PKT_NAK) + break; + + if (pkt->type == GIT_PKT_ACK && + (pkt->status != GIT_ACK_CONTINUE || + pkt->status != GIT_ACK_COMMON)) { + git__free(pkt); + return 0; + } + } + + git__free(pkt); + return 0; +} + +int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count) { transport_smart *t = (transport_smart *)transport; gitno_buffer *buf = &t->buffer; @@ -237,19 +281,20 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c unsigned int i; git_oid oid; - /* No own logic, do our thing */ - if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) return error; if ((error = fetch_setup_walk(&walk, repo)) < 0) goto on_error; + /* - * We don't support any kind of ACK extensions, so the negotiation - * boils down to sending what we have and listening for an ACK - * every once in a while. + * Our support for ACK extensions is simply to parse them. On + * the first ACK we will accept that as enough common + * objects. We give up if we haven't found an answer in the + * first 256 we send. */ i = 0; - while (true) { + while (i < 256) { error = git_revwalk_next(&oid, walk); if (error < 0) { @@ -278,7 +323,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c goto on_error; git_buf_clear(&data); - if (t->caps.multi_ack) { + if (t->caps.multi_ack || t->caps.multi_ack_detailed) { if ((error = store_common(t)) < 0) goto on_error; } else { @@ -307,7 +352,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c git_pkt_ack *pkt; unsigned int i; - if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) goto on_error; git_vector_foreach(&t->common, i, pkt) { @@ -327,7 +372,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c git_pkt_ack *pkt; unsigned int i; - if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) goto on_error; git_vector_foreach(&t->common, i, pkt) { @@ -356,7 +401,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c git_revwalk_free(walk); /* Now let's eat up whatever the server gives us */ - if (!t->caps.multi_ack) { + if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { pkt_type = recv_pkt(NULL, buf); if (pkt_type < 0) { @@ -366,22 +411,10 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c return -1; } } else { - git_pkt_ack *pkt; - do { - if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0) - return error; - - if (pkt->type == GIT_PKT_NAK || - (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { - git__free(pkt); - break; - } - - git__free(pkt); - } while (1); + error = wait_while_ack(buf); } - return 0; + return error; on_error: git_revwalk_free(walk); @@ -399,16 +432,16 @@ static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, return GIT_EUSER; } - if (writepack->add(writepack, buf->data, buf->offset, stats) < 0) + if (writepack->append(writepack, buf->data, buf->offset, stats) < 0) return -1; gitno_consume_n(buf, buf->offset); if ((recvd = gitno_recv(buf)) < 0) - return -1; + return recvd; } while(recvd > 0); - if (writepack->commit(writepack, stats)) + if (writepack->commit(writepack, stats) < 0) return -1; return 0; @@ -422,7 +455,7 @@ struct network_packetsize_payload size_t last_fired_bytes; }; -static void network_packetsize(size_t received, void *payload) +static int network_packetsize(size_t received, void *payload) { struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; @@ -432,8 +465,12 @@ static void network_packetsize(size_t received, void *payload) /* Fire notification if the threshold is reached */ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { npp->last_fired_bytes = npp->stats->received_bytes; - npp->callback(npp->stats, npp->payload); + + if (npp->callback(npp->stats, npp->payload)) + return GIT_EUSER; } + + return 0; } int git_smart__download_pack( @@ -447,7 +484,7 @@ int git_smart__download_pack( gitno_buffer *buf = &t->buffer; git_odb *odb; struct git_odb_writepack *writepack = NULL; - int error = -1; + int error = 0; struct network_packetsize_payload npp = {0}; memset(stats, 0, sizeof(git_transfer_progress)); @@ -460,13 +497,14 @@ int git_smart__download_pack( t->packetsize_payload = &npp; /* We might have something in the buffer already from negotiate_fetch */ - if (t->buffer.offset > 0) - t->packetsize_cb(t->buffer.offset, t->packetsize_payload); + if (t->buffer.offset > 0 && !t->cancelled.val) + if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) + git_atomic_set(&t->cancelled, 1); } if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)) - goto on_error; + goto done; /* * If the remote doesn't support the side-band, we can feed @@ -474,37 +512,46 @@ int git_smart__download_pack( * check which one belongs there. */ if (!t->caps.side_band && !t->caps.side_band_64k) { - if (no_sideband(t, writepack, buf, stats) < 0) - goto on_error; - - goto on_success; + error = no_sideband(t, writepack, buf, stats); + goto done; } do { git_pkt *pkt; + /* Check cancellation before network call */ if (t->cancelled.val) { giterr_set(GITERR_NET, "The fetch was cancelled by the user"); error = GIT_EUSER; - goto on_error; + goto done; } - if (recv_pkt(&pkt, buf) < 0) - goto on_error; + if ((error = recv_pkt(&pkt, buf)) < 0) + goto done; + + /* Check cancellation after network call */ + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto done; + } if (pkt->type == GIT_PKT_PROGRESS) { if (t->progress_cb) { git_pkt_progress *p = (git_pkt_progress *) pkt; - t->progress_cb(p->data, p->len, t->message_cb_payload); + if (t->progress_cb(p->data, p->len, t->message_cb_payload)) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + return GIT_EUSER; + } } git__free(pkt); } else if (pkt->type == GIT_PKT_DATA) { git_pkt_data *p = (git_pkt_data *) pkt; - error = writepack->add(writepack, p->data, p->len, stats); + error = writepack->append(writepack, p->data, p->len, stats); git__free(pkt); if (error < 0) - goto on_error; + goto done; } else if (pkt->type == GIT_PKT_FLUSH) { /* A flush indicates the end of the packfile */ git__free(pkt); @@ -512,13 +559,9 @@ int git_smart__download_pack( } } while (1); - if (writepack->commit(writepack, stats) < 0) - goto on_error; + error = writepack->commit(writepack, stats); -on_success: - error = 0; - -on_error: +done: if (writepack) writepack->free(writepack); @@ -656,7 +699,7 @@ static int parse_report(gitno_buffer *buf, git_push *push) if (error == GIT_EBUFS) { if ((recvd = gitno_recv(buf)) < 0) - return -1; + return recvd; if (recvd == 0) { giterr_set(GITERR_NET, "Early EOF"); @@ -801,22 +844,56 @@ static int update_refs_from_report( return 0; } +struct push_packbuilder_payload +{ + git_smart_subtransport_stream *stream; + git_packbuilder *pb; + git_push_transfer_progress cb; + void *cb_payload; + size_t last_bytes; + double last_progress_report_time; +}; + static int stream_thunk(void *buf, size_t size, void *data) { - git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data; + int error = 0; + struct push_packbuilder_payload *payload = data; - return s->write(s, (const char *)buf, size); + if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0) + return error; + + if (payload->cb) { + double current_time = git__timer(); + payload->last_bytes += size; + + if ((current_time - payload->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) { + payload->last_progress_report_time = current_time; + if (payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload)) { + giterr_clear(); + error = GIT_EUSER; + } + } + } + + return error; } int git_smart__push(git_transport *transport, git_push *push) { transport_smart *t = (transport_smart *)transport; - git_smart_subtransport_stream *s; + struct push_packbuilder_payload packbuilder_payload = {0}; git_buf pktline = GIT_BUF_INIT; - int error = -1, need_pack = 0; + int error = 0, need_pack = 0; push_spec *spec; unsigned int i; + packbuilder_payload.pb = push->pb; + + if (push->transfer_progress_cb) { + packbuilder_payload.cb = push->transfer_progress_cb; + packbuilder_payload.cb_payload = push->transfer_progress_cb_payload; + } + #ifdef PUSH_DEBUG { git_remote_head *head; @@ -848,29 +925,36 @@ int git_smart__push(git_transport *transport, git_push *push) } } - if (git_smart__get_push_stream(t, &s) < 0 || - gen_pktline(&pktline, push) < 0 || - s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0) - goto on_error; + if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || + (error = gen_pktline(&pktline, push)) < 0 || + (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline))) < 0) + goto done; - if (need_pack && git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0) - goto on_error; + if (need_pack && + (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) + goto done; /* If we sent nothing or the server doesn't support report-status, then * we consider the pack to have been unpacked successfully */ if (!push->specs.length || !push->report_status) push->unpack_ok = 1; - else if (parse_report(&t->buffer, push) < 0) - goto on_error; + else if ((error = parse_report(&t->buffer, push)) < 0) + goto done; - if (push->status.length && - update_refs_from_report(&t->refs, &push->specs, &push->status) < 0) - goto on_error; + /* If progress is being reported write the final report */ + if (push->transfer_progress_cb) { + push->transfer_progress_cb(push->pb->nr_written, push->pb->nr_objects, packbuilder_payload.last_bytes, push->transfer_progress_cb_payload); + } - error = 0; + if (push->status.length) { + error = update_refs_from_report(&t->refs, &push->specs, &push->status); + if (error < 0) + goto done; -on_error: + error = git_smart__update_heads(t); + } + +done: git_buf_free(&pktline); - return error; } diff --git a/src/transports/ssh.c b/src/transports/ssh.c index a312c8d08..4a905e3c9 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -5,19 +5,18 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifdef GIT_SSH - #include "git2.h" #include "buffer.h" #include "netops.h" #include "smart.h" +#ifdef GIT_SSH + #include #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) static const char prefix_ssh[] = "ssh://"; -static const char default_user[] = "git"; static const char cmd_uploadpack[] = "git-upload-pack"; static const char cmd_receivepack[] = "git-receive-pack"; @@ -38,6 +37,14 @@ typedef struct { git_cred *cred; } ssh_subtransport; +static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) +{ + char *ssherr; + libssh2_session_last_error(session, &ssherr, NULL, 0); + + giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr); +} + /* * Create a git protocol request. * @@ -46,27 +53,29 @@ typedef struct { static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *repo; - + if (!git__prefixcmp(url, prefix_ssh)) { url = url + strlen(prefix_ssh); repo = strchr(url, '/'); } else { repo = strchr(url, ':'); + if (repo) repo++; } - + if (!repo) { + giterr_set(GITERR_NET, "Malformed git protocol URL"); return -1; } - + int len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; - + git_buf_grow(request, len); git_buf_printf(request, "%s '%s'", cmd, repo); git_buf_putc(request, '\0'); - + if (git_buf_oom(request)) return -1; - + return 0; } @@ -74,21 +83,19 @@ static int send_command(ssh_stream *s) { int error; git_buf request = GIT_BUF_INIT; - + error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - - error = libssh2_channel_exec( - s->channel, - request.ptr - ); - if (0 != error) + error = libssh2_channel_exec(s->channel, request.ptr); + if (error < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not execute request"); goto cleanup; - + } + s->sent_command = 1; - + cleanup: git_buf_free(&request); return error; @@ -100,19 +107,21 @@ static int ssh_stream_read( size_t buf_size, size_t *bytes_read) { + int rc; ssh_stream *s = (ssh_stream *)stream; - + *bytes_read = 0; - + if (!s->sent_command && send_command(s) < 0) return -1; - - int rc = libssh2_channel_read(s->channel, buffer, buf_size); - if (rc < 0) + + if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read data");; return -1; - + } + *bytes_read = rc; - + return 0; } @@ -122,16 +131,16 @@ static int ssh_stream_write( size_t len) { ssh_stream *s = (ssh_stream *)stream; - + if (!s->sent_command && send_command(s) < 0) return -1; - - int rc = libssh2_channel_write(s->channel, buffer, len); - if (rc < 0) { + + if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not write data"); return -1; } - - return rc; + + return 0; } static void ssh_stream_free(git_smart_subtransport_stream *stream) @@ -139,26 +148,27 @@ static void ssh_stream_free(git_smart_subtransport_stream *stream) ssh_stream *s = (ssh_stream *)stream; ssh_subtransport *t = OWNING_SUBTRANSPORT(s); int ret; - + GIT_UNUSED(ret); - + t->current_stream = NULL; - + if (s->channel) { libssh2_channel_close(s->channel); - libssh2_channel_free(s->channel); - s->channel = NULL; + libssh2_channel_free(s->channel); + s->channel = NULL; } - + if (s->session) { - libssh2_session_free(s->session), s->session = NULL; + libssh2_session_free(s->session); + s->session = NULL; } - + if (s->socket.socket) { - ret = gitno_close(&s->socket); - assert(!ret); + (void)gitno_close(&s->socket); + /* can't do anything here with error return value */ } - + git__free(s->url); git__free(s); } @@ -170,26 +180,25 @@ static int ssh_stream_alloc( git_smart_subtransport_stream **stream) { ssh_stream *s; - - if (!stream) - return -1; - + + assert(stream); + s = git__calloc(sizeof(ssh_stream), 1); GITERR_CHECK_ALLOC(s); - + s->parent.subtransport = &t->parent; s->parent.read = ssh_stream_read; s->parent.write = ssh_stream_write; s->parent.free = ssh_stream_free; - + s->cmd = cmd; + s->url = git__strdup(url); - if (!s->url) { git__free(s); return -1; } - + *stream = &s->parent; return 0; } @@ -201,136 +210,127 @@ static int git_ssh_extract_url_parts( { char *colon, *at; const char *start; - - colon = strchr(url, ':'); - - if (colon == NULL) { - giterr_set(GITERR_NET, "Malformed URL: missing :"); - return -1; - } - + + colon = strchr(url, ':'); + + at = strchr(url, '@'); if (at) { - start = at+1; + start = at + 1; *username = git__substrdup(url, at - url); + GITERR_CHECK_ALLOC(*username); } else { start = url; - *username = git__strdup(default_user); + *username = NULL; } - + + if (colon == NULL || (colon < start)) { + giterr_set(GITERR_NET, "Malformed URL"); + return -1; + } + *host = git__substrdup(start, colon - start); - + GITERR_CHECK_ALLOC(*host); + return 0; } static int _git_ssh_authenticate_session( LIBSSH2_SESSION* session, const char *user, - git_cred* cred -) + git_cred* cred) { int rc; + do { switch (cred->credtype) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - rc = libssh2_userauth_password( - session, - c->username, - c->password - ); - break; - } - case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: { - git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred; - rc = libssh2_userauth_publickey_fromfile( - session, - user, - c->publickey, - c->privatekey, - c->passphrase - ); - break; - } - case GIT_CREDTYPE_SSH_PUBLICKEY: { - git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred; - rc = libssh2_userauth_publickey( - session, - user, - (const unsigned char *)c->publickey, - c->publickey_len, - c->sign_callback, - &c->sign_data - ); - break; - } - default: - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + case GIT_CREDTYPE_USERPASS_PLAINTEXT: { + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + user = c->username ? c->username : user; + rc = libssh2_userauth_password(session, user, c->password); + break; } - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - return rc; -} + case GIT_CREDTYPE_SSH_KEY: { + git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + user = c->username ? c->username : user; + rc = libssh2_userauth_publickey_fromfile( + session, c->username, c->publickey, c->privatekey, c->passphrase); + break; + } + case GIT_CREDTYPE_SSH_CUSTOM: { + git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; -static int _git_ssh_session_create -( - LIBSSH2_SESSION** session, - gitno_socket socket -) -{ - if (!session) { + user = c->username ? c->username : user; + rc = libssh2_userauth_publickey( + session, c->username, (const unsigned char *)c->publickey, + c->publickey_len, c->sign_callback, &c->sign_data); + break; + } + default: + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + } + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(session, "Failed to authenticate SSH session"); return -1; } - - LIBSSH2_SESSION* s = libssh2_session_init(); - if (!s) - return -1; - - int rc = 0; - do { - rc = libssh2_session_startup(s, socket.socket); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (0 != rc) { - goto on_error; - } - - libssh2_session_set_blocking(s, 1); - - *session = s; - + return 0; - -on_error: - if (s) { - libssh2_session_free(s), s = NULL; +} + +static int _git_ssh_session_create( + LIBSSH2_SESSION** session, + gitno_socket socket) +{ + int rc = 0; + LIBSSH2_SESSION* s; + + assert(session); + + s = libssh2_session_init(); + if (!s) { + giterr_set(GITERR_NET, "Failed to initialize SSH session"); + return -1; } - - return -1; + + do { + rc = libssh2_session_startup(s, socket.socket); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "Failed to start SSH session"); + libssh2_session_free(s); + return -1; + } + + libssh2_session_set_blocking(s, 1); + + *session = s; + + return 0; } static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, - git_smart_subtransport_stream **stream -) + git_smart_subtransport_stream **stream) { - char *host, *port=NULL, *user=NULL, *pass=NULL; + char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; ssh_stream *s; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; - + *stream = NULL; if (ssh_stream_alloc(t, url, cmd, stream) < 0) return -1; - + s = (ssh_stream *)*stream; - + if (!git__prefixcmp(url, prefix_ssh)) { - url = url + strlen(prefix_ssh); - if (gitno_extract_url_parts(&host, &port, &user, &pass, url, default_port) < 0) + if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0) goto on_error; } else { if (git_ssh_extract_url_parts(&host, &user, url) < 0) @@ -338,61 +338,78 @@ static int _git_ssh_setup_conn( port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } - + if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; - + if (user && pass) { if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) goto on_error; - } else { - if (t->owner->cred_acquire_cb(&t->cred, - t->owner->url, - user, - GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, + } else if (t->owner->cred_acquire_cb) { + if (t->owner->cred_acquire_cb( + &t->cred, t->owner->url, user, + GIT_CREDTYPE_USERPASS_PLAINTEXT | + GIT_CREDTYPE_SSH_KEY | + GIT_CREDTYPE_SSH_CUSTOM, t->owner->cred_acquire_payload) < 0) - return -1; + goto on_error; + + if (!t->cred) { + giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials"); + goto on_error; + } + } else { + giterr_set(GITERR_SSH, "Cannot set up SSH connection without credentials"); + goto on_error; } assert(t->cred); - - if (!user) { - user = git__strdup(default_user); + + if (!user && !git_cred_has_username(t->cred)) { + giterr_set_str(GITERR_NET, "Cannot authenticate without a username"); + goto on_error; } - + if (_git_ssh_session_create(&session, s->socket) < 0) goto on_error; - - if (_git_ssh_authenticate_session(session, user, t->cred) < 0) + + if (_git_ssh_authenticate_session(session, user, t->cred) < 0) goto on_error; - + channel = libssh2_channel_open_session(session); - if (!channel) - goto on_error; - + if (!channel) { + ssh_error(session, "Failed to open SSH channel"); + goto on_error; + } + libssh2_channel_set_blocking(channel, 1); - + s->session = session; s->channel = channel; - + t->current_stream = s; git__free(host); git__free(port); + git__free(path); git__free(user); git__free(pass); return 0; - + on_error: + s->session = NULL; + s->channel = NULL; + t->current_stream = NULL; + if (*stream) ssh_stream_free(*stream); - + git__free(host); git__free(port); git__free(user); git__free(pass); if (session) - libssh2_session_free(session), session = NULL; + libssh2_session_free(session); return -1; } @@ -404,7 +421,7 @@ static int ssh_uploadpack_ls( { if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0) return -1; - + return 0; } @@ -414,12 +431,12 @@ static int ssh_uploadpack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); return -1; } @@ -431,7 +448,7 @@ static int ssh_receivepack_ls( { if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0) return -1; - + return 0; } @@ -441,12 +458,12 @@ static int ssh_receivepack( git_smart_subtransport_stream **stream) { GIT_UNUSED(url); - + if (t->current_stream) { *stream = &t->current_stream->parent; return 0; } - + giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); return -1; } @@ -458,21 +475,21 @@ static int _ssh_action( git_smart_service_t action) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + switch (action) { case GIT_SERVICE_UPLOADPACK_LS: return ssh_uploadpack_ls(t, url, stream); - + case GIT_SERVICE_UPLOADPACK: return ssh_uploadpack(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK_LS: return ssh_receivepack_ls(t, url, stream); - + case GIT_SERVICE_RECEIVEPACK: return ssh_receivepack(t, url, stream); } - + *stream = NULL; return -1; } @@ -480,40 +497,49 @@ static int _ssh_action( static int _ssh_close(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + GIT_UNUSED(t); - + return 0; } static void _ssh_free(git_smart_subtransport *subtransport) { ssh_subtransport *t = (ssh_subtransport *) subtransport; - + assert(!t->current_stream); - + git__free(t); } +#endif -int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner) +int git_smart_subtransport_ssh( + git_smart_subtransport **out, git_transport *owner) { +#ifdef GIT_SSH ssh_subtransport *t; - - if (!out) - return -1; - + + assert(out); + t = git__calloc(sizeof(ssh_subtransport), 1); GITERR_CHECK_ALLOC(t); - + t->owner = (transport_smart *)owner; t->parent.action = _ssh_action; t->parent.close = _ssh_close; t->parent.free = _ssh_free; - + *out = (git_smart_subtransport *) t; return 0; -} +#else + GIT_UNUSED(owner); + assert(out); + *out = NULL; + + giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support"); + return -1; #endif +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 95e422dc0..673cd0faf 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -52,6 +52,7 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | typedef enum { GIT_WINHTTP_AUTH_BASIC = 1, + GIT_WINHTTP_AUTH_NEGOTIATE = 2, } winhttp_authmechanism_t; typedef struct { @@ -73,17 +74,12 @@ typedef struct { typedef struct { git_smart_subtransport parent; transport_smart *owner; - const char *path; - char *host; - char *port; - char *user_from_url; - char *pass_from_url; + gitno_connection_data connection_data; git_cred *cred; git_cred *url_cred; int auth_mechanism; HINTERNET session; HINTERNET connection; - unsigned use_ssl : 1; } winhttp_subtransport; static int apply_basic_credential(HINTERNET request, git_cred *cred) @@ -143,6 +139,22 @@ on_error: return error; } +static int apply_default_credentials(HINTERNET request) +{ + /* If we are explicitly asked to deliver default credentials, turn set + * the security level to low which will guarantee they are delivered. + * The default is "medium" which applies to the intranet and sounds + * like it would correspond to Internet Explorer security zones, but + * in fact does not. + */ + DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + + if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD))) + return -1; + + return 0; +} + static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); @@ -152,9 +164,10 @@ static int winhttp_stream_connect(winhttp_stream *s) wchar_t *types[] = { L"*/*", NULL }; BOOL peerdist = FALSE; int error = -1, wide_len; + unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; /* Prepare URL */ - git_buf_printf(&buf, "%s%s", t->path, s->service_url); + git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); if (git_buf_oom(&buf)) return -1; @@ -187,7 +200,7 @@ static int winhttp_stream_connect(winhttp_stream *s) NULL, WINHTTP_NO_REFERER, types, - t->use_ssl ? WINHTTP_FLAG_SECURE : 0); + t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0); if (!s->request) { giterr_set(GITERR_OS, "Failed to open request"); @@ -195,7 +208,7 @@ static int winhttp_stream_connect(winhttp_stream *s) } /* Set proxy if necessary */ - if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0) + if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0) goto on_error; if (proxy_url) { @@ -244,6 +257,17 @@ static int winhttp_stream_connect(winhttp_stream *s) git__free(proxy_wide); } + /* Disable WinHTTP redirects so we can handle them manually. Why, you ask? + * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae + */ + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_DISABLE_FEATURE, + &disable_redirects, + sizeof(disable_redirects))) { + giterr_set(GITERR_OS, "Failed to disable redirects"); + goto on_error; + } + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP * adds itself. This option may not be supported by the underlying * platform, so we do not error-check it */ @@ -258,22 +282,39 @@ static int winhttp_stream_connect(winhttp_stream *s) goto on_error; } - /* Send Content-Type header -- only necessary on a POST */ if (post_verb == s->verb) { + /* Send Content-Type and Accept headers -- only necessary on a POST */ git_buf_clear(&buf); - if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) + if (git_buf_printf(&buf, + "Content-Type: application/x-git-%s-request", + s->service) < 0) goto on_error; git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + 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; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, + "Accept: application/x-git-%s-result", + s->service) < 0) + goto on_error; + + git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + + 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->use_ssl) { + if (t->connection_data.use_ssl) { int flags; if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) @@ -293,12 +334,17 @@ static int winhttp_stream_connect(winhttp_stream *s) t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC && apply_basic_credential(s->request, t->cred) < 0) goto on_error; + else if (t->cred && + t->cred->credtype == GIT_CREDTYPE_DEFAULT && + t->auth_mechanism == GIT_WINHTTP_AUTH_NEGOTIATE && + apply_default_credentials(s->request) < 0) + goto on_error; /* If no other credentials have been applied and the URL has username and * password, use those */ - if (!t->cred && t->user_from_url && t->pass_from_url) { + if (!t->cred && t->connection_data.user && t->connection_data.pass) { if (!t->url_cred && - git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0) + git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0) goto on_error; if (apply_basic_credential(s->request, t->url_cred) < 0) goto on_error; @@ -337,6 +383,12 @@ static int parse_unauthorized_response( *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; } + if ((WINHTTP_AUTH_SCHEME_NTLM & supported) || + (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported)) { + *allowed_types |= GIT_CREDTYPE_DEFAULT; + *auth_mechanism = GIT_WINHTTP_AUTH_NEGOTIATE; + } + return 0; } @@ -380,6 +432,50 @@ static int write_chunk(HINTERNET request, const char *buffer, size_t len) return 0; } +static int winhttp_connect( + winhttp_subtransport *t, + const char *url) +{ + wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; + git_win32_path host; + int32_t port; + const char *default_port = "80"; + + /* Prepare port */ + if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0) + return -1; + + /* Prepare host */ + git_win32_path_from_c(host, t->connection_data.host); + + /* Establish session */ + t->session = WinHttpOpen( + ua, + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!t->session) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + return -1; + } + + /* Establish connection */ + t->connection = WinHttpConnect( + t->session, + host, + (INTERNET_PORT) port, + 0); + + if (!t->connection) { + giterr_set(GITERR_OS, "Failed to connect to host"); + return -1; + } + + return 0; +} + static int winhttp_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -511,50 +607,53 @@ replay: /* Check for Windows 7. This workaround is only necessary on * Windows Vista and earlier. Windows 7 is version 6.1. */ - if (!git_has_win32_version(6, 1)) { - wchar_t *location; - DWORD location_length; - int redirect_cmp; + wchar_t *location; + DWORD location_length; + char *location8; - /* OK, fetch the Location header from the redirect. */ - if (WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - WINHTTP_NO_OUTPUT_BUFFER, - &location_length, - WINHTTP_NO_HEADER_INDEX) || - GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - giterr_set(GITERR_OS, "Failed to read Location header"); - return -1; - } - - location = git__malloc(location_length); - GITERR_CHECK_ALLOC(location); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - location, - &location_length, - WINHTTP_NO_HEADER_INDEX)) { - giterr_set(GITERR_OS, "Failed to read Location header"); - git__free(location); - return -1; - } - - /* Compare the Location header with the request URI */ - redirect_cmp = wcscmp(location, s->request_uri); - git__free(location); - - if (!redirect_cmp) { - /* Replay the request */ - WinHttpCloseHandle(s->request); - s->request = NULL; - s->sent_request = 0; - - goto replay; - } + /* OK, fetch the Location header from the redirect. */ + if (WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + WINHTTP_NO_OUTPUT_BUFFER, + &location_length, + WINHTTP_NO_HEADER_INDEX) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + giterr_set(GITERR_OS, "Failed to read Location header"); + return -1; } + + location = git__malloc(location_length); + location8 = git__malloc(location_length); + GITERR_CHECK_ALLOC(location); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + location, + &location_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to read Location header"); + git__free(location); + return -1; + } + git__utf16_to_8(location8, location_length, location); + git__free(location); + + /* Replay the request */ + WinHttpCloseHandle(s->request); + s->request = NULL; + s->sent_request = 0; + + if (!git__prefixcmp_icase(location8, prefix_https)) { + /* Upgrade to secure connection; disconnect and start over */ + if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) + return -1; + winhttp_connect(t, location8); + } + + git__free(location8); + goto replay; } /* Handle authentication failures */ @@ -568,8 +667,9 @@ replay: if (allowed_types && (!t->cred || 0 == (t->cred->credtype & allowed_types))) { - if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->user_from_url, allowed_types, t->owner->cred_acquire_payload) < 0) - return -1; + if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->connection_data.user, allowed_types, + t->owner->cred_acquire_payload) < 0) + return GIT_EUSER; assert(t->cred); @@ -888,68 +988,6 @@ static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream return 0; } -static int winhttp_connect( - winhttp_subtransport *t, - const char *url) -{ - wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t host[GIT_WIN_PATH]; - int32_t port; - const char *default_port = "80"; - int ret; - - if (!git__prefixcmp(url, prefix_http)) { - url = url + strlen(prefix_http); - default_port = "80"; - } - - if (!git__prefixcmp(url, prefix_https)) { - url += strlen(prefix_https); - default_port = "443"; - t->use_ssl = 1; - } - - if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url, - &t->pass_from_url, url, default_port)) < 0) - return ret; - - t->path = strchr(url, '/'); - - /* Prepare port */ - if (git__strtol32(&port, t->port, NULL, 10) < 0) - return -1; - - /* Prepare host */ - git__utf8_to_16(host, GIT_WIN_PATH, t->host); - - /* Establish session */ - t->session = WinHttpOpen( - ua, - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - 0); - - if (!t->session) { - giterr_set(GITERR_OS, "Failed to init WinHTTP"); - return -1; - } - - /* Establish connection */ - t->connection = WinHttpConnect( - t->session, - host, - port, - 0); - - if (!t->connection) { - giterr_set(GITERR_OS, "Failed to connect to host"); - return -1; - } - - return 0; -} - static int winhttp_uploadpack_ls( winhttp_subtransport *t, winhttp_stream *s) @@ -989,7 +1027,7 @@ static int winhttp_receivepack( { /* WinHTTP only supports Transfer-Encoding: chunked * on Windows Vista (NT 6.0) and higher. */ - s->chunked = git_has_win32_version(6, 0); + s->chunked = git_has_win32_version(6, 0, 0); if (s->chunked) s->parent.write = winhttp_stream_write_chunked; @@ -1013,9 +1051,10 @@ static int winhttp_action( winhttp_stream *s; int ret = -1; - if (!t->connection && - winhttp_connect(t, url) < 0) - return -1; + if (!t->connection) + if (gitno_connection_data_from_url(&t->connection_data, url, NULL) < 0 || + winhttp_connect(t, url) < 0) + return -1; if (winhttp_stream_alloc(t, &s) < 0) return -1; @@ -1056,25 +1095,7 @@ static int winhttp_close(git_smart_subtransport *subtransport) winhttp_subtransport *t = (winhttp_subtransport *)subtransport; int ret = 0; - if (t->host) { - git__free(t->host); - t->host = NULL; - } - - if (t->port) { - git__free(t->port); - t->port = NULL; - } - - if (t->user_from_url) { - git__free(t->user_from_url); - t->user_from_url = NULL; - } - - if (t->pass_from_url) { - git__free(t->pass_from_url); - t->pass_from_url = NULL; - } + gitno_connection_data_free_ptrs(&t->connection_data); if (t->cred) { t->cred->free(t->cred); diff --git a/src/tree-cache.c b/src/tree-cache.c index 97ffc2acf..1d3997154 100644 --- a/src/tree-cache.c +++ b/src/tree-cache.c @@ -138,9 +138,11 @@ static int read_tree_internal(git_tree_cache **out, tree->children = git__malloc(tree->children_count * sizeof(git_tree_cache *)); GITERR_CHECK_ALLOC(tree->children); + memset(tree->children, 0x0, tree->children_count * sizeof(git_tree_cache *)); + for (i = 0; i < tree->children_count; ++i) { if (read_tree_internal(&tree->children[i], &buffer, buffer_end, tree) < 0) - return -1; + goto corrupted; } } @@ -150,7 +152,7 @@ static int read_tree_internal(git_tree_cache **out, corrupted: git_tree_cache_free(tree); - giterr_set(GITERR_INDEX, "Corruped TREE extension in index"); + giterr_set(GITERR_INDEX, "Corrupted TREE extension in index"); return -1; } @@ -162,7 +164,7 @@ int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer return -1; if (buffer < buffer_end) { - giterr_set(GITERR_INDEX, "Corruped TREE extension in index (unexpected trailing data)"); + giterr_set(GITERR_INDEX, "Corrupted TREE extension in index (unexpected trailing data)"); return -1; } @@ -176,9 +178,12 @@ void git_tree_cache_free(git_tree_cache *tree) if (tree == NULL) return; - for (i = 0; i < tree->children_count; ++i) - git_tree_cache_free(tree->children[i]); + if (tree->children != NULL) { + for (i = 0; i < tree->children_count; ++i) + git_tree_cache_free(tree->children[i]); + + git__free(tree->children); + } - git__free(tree->children); git__free(tree); } diff --git a/src/tree.c b/src/tree.c index 65d01b4d5..bb59ff82b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -10,7 +10,7 @@ #include "tree.h" #include "git2/repository.h" #include "git2/object.h" -#include "path.h" +#include "fileops.h" #include "tree-cache.h" #include "index.h" @@ -29,19 +29,19 @@ static bool valid_filemode(const int filemode) GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) { /* Tree bits set, but it's not a commit */ - if (filemode & GIT_FILEMODE_TREE && !(filemode & 0100000)) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) return GIT_FILEMODE_TREE; - /* If any of the x bits is set */ - if (filemode & 0111) + /* If any of the x bits are set */ + if (GIT_PERMS_IS_EXEC(filemode)) return GIT_FILEMODE_BLOB_EXECUTABLE; /* 16XXXX means commit */ - if ((filemode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) return GIT_FILEMODE_COMMIT; /* 12XXXX means commit */ - if ((filemode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK) + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) return GIT_FILEMODE_LINK; /* Otherwise, return a blob */ @@ -237,7 +237,12 @@ void git_tree__free(void *_tree) git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) { - return (git_filemode_t)entry->attr; + return normalize_filemode(entry->attr); +} + +git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) +{ + return entry->attr; } const char *git_tree_entry_name(const git_tree_entry *entry) @@ -386,8 +391,6 @@ int git_tree__parse(void *_tree, git_odb_object *odb_obj) if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer) return tree_error("Failed to parse tree. Can't parse filemode", NULL); - attr = normalize_filemode(attr); /* make sure to normalize the filemode */ - if (*buffer++ != ' ') return tree_error("Failed to parse tree. Object is corrupted", NULL); @@ -881,8 +884,10 @@ static int tree_walk( git_vector_foreach(&tree->entries, i, entry) { if (preorder) { error = callback(path->ptr, entry, payload); - if (error > 0) + if (error > 0) { + error = 0; continue; + } if (error < 0) { giterr_clear(); return GIT_EUSER; @@ -905,11 +910,12 @@ static int tree_walk( return -1; error = tree_walk(subtree, callback, path, payload, preorder); + git_tree_free(subtree); + if (error != 0) break; git_buf_truncate(path, path_len); - git_tree_free(subtree); } if (!preorder && callback(path->ptr, entry, payload) < 0) { diff --git a/src/util.c b/src/util.c index c543a3d21..47516a8f7 100644 --- a/src/util.c +++ b/src/util.c @@ -32,6 +32,9 @@ int git_libgit2_capabilities() #endif #if defined(GIT_SSL) || defined(GIT_WINHTTP) | GIT_CAP_HTTPS +#endif +#if defined(GIT_SSH) + | GIT_CAP_SSH #endif ; } @@ -114,6 +117,19 @@ int git_libgit2_opts(int key, ...) *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; *(va_arg(ap, ssize_t *)) = git_cache__max_storage; break; + + case GIT_OPT_GET_TEMPLATE_PATH: + { + char *out = va_arg(ap, char *); + size_t outlen = va_arg(ap, size_t); + + error = git_futils_dirs_get_str(out, outlen, GIT_FUTILS_DIR_TEMPLATE); + } + break; + + case GIT_OPT_SET_TEMPLATE_PATH: + error = git_futils_dirs_set(GIT_FUTILS_DIR_TEMPLATE, va_arg(ap, const char *)); + break; } va_end(ap); @@ -676,6 +692,9 @@ size_t git__unescape(char *str) { char *scan, *pos = str; + if (!str) + return 0; + for (scan = str; *scan; pos++, scan++) { if (*scan == '\\' && *(scan + 1) != '\0') scan++; /* skip '\' but include next char */ @@ -707,8 +726,9 @@ 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(__OpenBSD__) || defined(AMIGA) || \ - defined(__gnu_hurd__) || \ +#if defined(__MINGW32__) || defined(AMIGA) || \ + defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__gnu_hurd__) || defined(__ANDROID_API__) || \ (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) git__insertsort_r(els, nel, elsize, NULL, cmp, payload); #elif defined(GIT_WIN32) diff --git a/src/util.h b/src/util.h index e0088399c..f9de909e9 100644 --- a/src/util.h +++ b/src/util.h @@ -55,6 +55,9 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) ptr = (char*)git__malloc(length + 1); + if (!ptr) + return NULL; + if (length) memcpy(ptr, str, length); @@ -79,7 +82,10 @@ GIT_INLINE(void *) git__realloc(void *ptr, size_t size) return new_ptr; } -#define git__free(ptr) free(ptr) +GIT_INLINE(void) git__free(void *ptr) +{ + free(ptr); +} #define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) @@ -221,6 +227,9 @@ typedef void (*git_refcount_freeptr)(void *r); #define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner) +#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount) + + static signed char from_hex[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ @@ -291,6 +300,11 @@ GIT_INLINE(bool) git__isspace(int c) return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); } +GIT_INLINE(bool) git__isspace_nonlf(int c) +{ + return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); +} + GIT_INLINE(bool) git__iswildcard(int c) { return (c == '*' || c == '?' || c == '['); @@ -339,4 +353,65 @@ GIT_INLINE(void) git__memzero(void *data, size_t size) #endif } +#ifdef GIT_WIN32 + +GIT_INLINE(double) git__timer(void) +{ + /* We need the initial tick count to detect if the tick + * count has rolled over. */ + static DWORD initial_tick_count = 0; + + /* GetTickCount returns the number of milliseconds that have + * elapsed since the system was started. */ + DWORD count = GetTickCount(); + + if(initial_tick_count == 0) { + initial_tick_count = count; + } else if (count < initial_tick_count) { + /* The tick count has rolled over - adjust for it. */ + count = (0xFFFFFFFF - initial_tick_count) + count; + } + + return (double) count / (double) 1000; +} + +#elif __APPLE__ + +#include + +GIT_INLINE(double) git__timer(void) +{ + uint64_t time = mach_absolute_time(); + static double scaling_factor = 0; + + if (scaling_factor == 0) { + mach_timebase_info_data_t info; + (void)mach_timebase_info(&info); + scaling_factor = (double)info.numer / (double)info.denom; + } + + return (double)time * scaling_factor / 1.0E-9; +} + +#else + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { + return (double) tp.tv_sec + (double) tp.tv_nsec / 1E-9; + } else { + /* Fall back to using gettimeofday */ + struct timeval tv; + struct timezone tz; + gettimeofday(&tv, &tz); + return (double)tv.tv_sec + (double)tv.tv_usec / 1E-6; + } +} + +#endif + #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 5ba2fab18..362e7b0c0 100644 --- a/src/vector.c +++ b/src/vector.c @@ -220,7 +220,7 @@ void git_vector_pop(git_vector *v) v->length--; } -void git_vector_uniq(git_vector *v) +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) { git_vector_cmp cmp; size_t i, j; @@ -232,9 +232,12 @@ void git_vector_uniq(git_vector *v) cmp = v->_cmp ? v->_cmp : strict_comparison; for (i = 0, j = 1 ; j < v->length; ++j) - if (!cmp(v->contents[i], v->contents[j])) + if (!cmp(v->contents[i], v->contents[j])) { + if (git_free_cb) + git_free_cb(v->contents[i]); + v->contents[i] = v->contents[j]; - else + } else v->contents[++i] = v->contents[j]; v->length -= j - i - 1; diff --git a/src/vector.h b/src/vector.h index 1bda9c93d..279f5c6ee 100644 --- a/src/vector.h +++ b/src/vector.h @@ -55,6 +55,11 @@ GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) #define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) +GIT_INLINE(size_t) git_vector_length(const git_vector *v) +{ + return v->length; +} + GIT_INLINE(void *) git_vector_last(const git_vector *v) { return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; @@ -71,7 +76,7 @@ int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, size_t idx); void git_vector_pop(git_vector *v); -void git_vector_uniq(git_vector *v); +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); void git_vector_remove_matching( git_vector *v, int (*match)(const git_vector *v, size_t idx)); diff --git a/src/win32/dir.c b/src/win32/dir.c index 8c51d8378..f7859b73f 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -5,8 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ #define GIT__WIN32_NO_WRAP_DIR -#include "dir.h" -#include "utf-conv.h" +#include "posix.h" static int init_filter(char *filter, size_t n, const char *dir) { @@ -25,36 +24,32 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { - char filter[GIT_WIN_PATH]; - wchar_t filter_w[GIT_WIN_PATH]; + git_win32_path_as_utf8 filter; + git_win32_path filter_w; git__DIR *new = NULL; + size_t dirlen; if (!dir || !init_filter(filter, sizeof(filter), dir)) return NULL; - new = git__calloc(1, sizeof(*new)); + dirlen = strlen(dir); + + new = git__calloc(sizeof(*new) + dirlen + 1, 1); if (!new) return NULL; + memcpy(new->dir, dir, dirlen); - new->dir = git__strdup(dir); - if (!new->dir) - goto fail; - - git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); + git_win32_path_from_c(filter_w, filter); new->h = FindFirstFileW(filter_w, &new->f); if (new->h == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Could not open directory '%s'", dir); - goto fail; + git__free(new); + return NULL; } new->first = 1; return new; - -fail: - git__free(new->dir); - git__free(new); - return NULL; } int git__readdir_ext( @@ -80,7 +75,7 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; - git__utf16_to_8(entry->d_name, d->f.cFileName); + git_win32_path_to_c(entry->d_name, d->f.cFileName); entry->d_ino = 0; *result = entry; @@ -101,8 +96,8 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { - char filter[GIT_WIN_PATH]; - wchar_t filter_w[GIT_WIN_PATH]; + git_win32_path_as_utf8 filter; + git_win32_path filter_w; if (!d) return; @@ -116,7 +111,7 @@ void git__rewinddir(git__DIR *d) if (!init_filter(filter, sizeof(filter), d->dir)) return; - git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); + git_win32_path_from_c(filter_w, filter); d->h = FindFirstFileW(filter_w, &d->f); if (d->h == INVALID_HANDLE_VALUE) @@ -134,8 +129,7 @@ int git__closedir(git__DIR *d) FindClose(d->h); d->h = INVALID_HANDLE_VALUE; } - git__free(d->dir); - d->dir = NULL; + git__free(d); return 0; } diff --git a/src/win32/dir.h b/src/win32/dir.h index 7696d468e..24d48f6ba 100644 --- a/src/win32/dir.h +++ b/src/win32/dir.h @@ -11,15 +11,15 @@ struct git__dirent { int d_ino; - char d_name[261]; + git_win32_path_as_utf8 d_name; }; typedef struct { HANDLE h; WIN32_FIND_DATAW f; struct git__dirent entry; - char *dir; int first; + char dir[GIT_FLEX_ARRAY]; } git__DIR; extern git__DIR *git__opendir(const char *); diff --git a/src/win32/error.c b/src/win32/error.c index a62a07e82..bc598ae32 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -47,7 +47,7 @@ char *git_win32_get_error_message(DWORD error_code) (LPWSTR)&lpMsgBuf, 0, NULL)) { /* Invalid code point check supported on Vista+ only */ - if (git_has_win32_version(6, 0)) + if (git_has_win32_version(6, 0, 0)) dwFlags = WC_ERR_INVALID_CHARS; else dwFlags = 0; diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 5dd3de13d..a9e812e28 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -23,11 +23,11 @@ int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ) return s_root->len ? 0 : -1; } -static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16) +static int win32_path_to_8(git_buf *path_utf8, const wchar_t *path) { char temp_utf8[GIT_PATH_MAX]; - git__utf16_to_8(temp_utf8, path_utf16); + git__utf16_to_8(temp_utf8, GIT_PATH_MAX, path); git_path_mkposix(temp_utf8); return git_buf_sets(path_utf8, temp_utf8); @@ -53,7 +53,7 @@ int git_win32__find_file( if (*filename == '/' || *filename == '\\') filename++; - git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename); + git__utf8_to_16(file_utf16 + root->len - 1, alloc_len - root->len, filename); /* check access */ if (_waccess(file_utf16, F_OK) < 0) { @@ -61,7 +61,7 @@ int git_win32__find_file( return GIT_ENOTFOUND; } - win32_path_utf16_to_8(path, file_utf16); + win32_path_to_8(path, file_utf16); git__free(file_utf16); return 0; @@ -86,7 +86,7 @@ static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen) return (path != base) ? path : NULL; } -static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe) +static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir) { wchar_t *env = _wgetenv(L"PATH"), lastch; struct git_win32__path root; @@ -110,10 +110,10 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe) wcscpy(&root.path[root.len], gitexe); if (_waccess(root.path, F_OK) == 0 && root.len > 5) { - /* replace "bin\\" or "cmd\\" with "etc\\" */ - wcscpy(&root.path[root.len - 4], L"etc\\"); + /* replace "bin\\" or "cmd\\" with subdir */ + wcscpy(&root.path[root.len - 4], subdir); - win32_path_utf16_to_8(buf, root.path); + win32_path_to_8(buf, root.path); return 0; } } @@ -122,7 +122,7 @@ static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe) } static int win32_find_git_in_registry( - git_buf *buf, const HKEY hieve, const wchar_t *key) + git_buf *buf, const HKEY hieve, const wchar_t *key, const wchar_t *subdir) { HKEY hKey; DWORD dwType = REG_SZ; @@ -130,9 +130,9 @@ static int win32_find_git_in_registry( assert(buf); - path16.len = 0; + path16.len = MAX_PATH; - if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { + if (RegOpenKeyExW(hieve, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS) { @@ -143,10 +143,10 @@ static int win32_find_git_in_registry( return -1; } - wcscat(path16.path, L"etc\\"); + wcscat(path16.path, subdir); path16.len += 4; - win32_path_utf16_to_8(buf, path16.path); + win32_path_to_8(buf, path16.path); } RegCloseKey(hKey); @@ -156,7 +156,7 @@ static int win32_find_git_in_registry( } static int win32_find_existing_dirs( - git_buf *out, const wchar_t *tmpl[], char *temp[]) + git_buf *out, const wchar_t *tmpl[]) { struct git_win32__path path16; git_buf buf = GIT_BUF_INIT; @@ -168,7 +168,7 @@ static int win32_find_existing_dirs( path16.path[0] != L'%' && !_waccess(path16.path, F_OK)) { - win32_path_utf16_to_8(&buf, path16.path); + win32_path_to_8(&buf, path16.path); if (buf.size) git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); @@ -180,26 +180,26 @@ static int win32_find_existing_dirs( return (git_buf_oom(out) ? -1 : 0); } -int git_win32__find_system_dirs(git_buf *out) +int git_win32__find_system_dirs(git_buf *out, const wchar_t *subdir) { git_buf buf = GIT_BUF_INIT; /* directories where git.exe & git.cmd are found */ - if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size) + if (!win32_find_git_in_path(&buf, L"git.exe", subdir) && buf.size) git_buf_set(out, buf.ptr, buf.size); else git_buf_clear(out); - if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size) + if (!win32_find_git_in_path(&buf, L"git.cmd", subdir) && buf.size) git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); /* directories where git is installed according to registry */ if (!win32_find_git_in_registry( - &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size) + &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size) git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); if (!win32_find_git_in_registry( - &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size) + &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size) git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); git_buf_free(&buf); @@ -209,7 +209,6 @@ int git_win32__find_system_dirs(git_buf *out) int git_win32__find_global_dirs(git_buf *out) { - char *temp[3]; static const wchar_t *global_tmpls[4] = { L"%HOME%\\", L"%HOMEDRIVE%%HOMEPATH%\\", @@ -217,12 +216,11 @@ int git_win32__find_global_dirs(git_buf *out) NULL, }; - return win32_find_existing_dirs(out, global_tmpls, temp); + return win32_find_existing_dirs(out, global_tmpls); } int git_win32__find_xdg_dirs(git_buf *out) { - char *temp[6]; static const wchar_t *global_tmpls[7] = { L"%XDG_CONFIG_HOME%\\git", L"%APPDATA%\\git", @@ -233,5 +231,5 @@ int git_win32__find_xdg_dirs(git_buf *out) NULL, }; - return win32_find_existing_dirs(out, global_tmpls, temp); + return win32_find_existing_dirs(out, global_tmpls); } diff --git a/src/win32/findfile.h b/src/win32/findfile.h index fc79e1b72..11bf7e620 100644 --- a/src/win32/findfile.h +++ b/src/win32/findfile.h @@ -19,7 +19,7 @@ extern int git_win32__expand_path( extern int git_win32__find_file( git_buf *path, const struct git_win32__path *root, const char *filename); -extern int git_win32__find_system_dirs(git_buf *out); +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); diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 7b97b48db..fe0abfb54 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -19,6 +19,11 @@ # define S_IFLNK _S_IFLNK # define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (size_t)(end - s) : maxlen; +} + #endif #endif /* INCLUDE_mingw_compat__ */ diff --git a/src/win32/posix.h b/src/win32/posix.h index c49c2175c..24cba23e0 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -8,7 +8,16 @@ #define INCLUDE_posix__w32_h__ #include "common.h" +#include "../posix.h" #include "utf-conv.h" +#include "dir.h" + +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ + +#ifndef EAFNOSUPPORT +# define EAFNOSUPPORT (INT_MAX-1) +#endif GIT_INLINE(int) p_link(const char *old, const char *new) { @@ -20,9 +29,9 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; + git_win32_path buf; GIT_UNUSED(mode); - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path_from_c(buf, path); return _wmkdir(buf); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index f04974428..18f717b0f 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -16,8 +16,8 @@ int p_unlink(const char *path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); _wchmod(buf, 0666); return _wunlink(buf); } @@ -59,10 +59,11 @@ static int do_lstat( const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t fbuf[GIT_WIN_PATH], lastch; + git_win32_path fbuf; + wchar_t lastch; int flen; - flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name); + flen = git_win32_path_from_c(fbuf, file_name); /* truncate trailing slashes */ for (; flen > 0; --flen) { @@ -90,6 +91,9 @@ static int do_lstat( if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) fMode |= S_IFLNK; + if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction + fMode ^= S_IFLNK; + buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; @@ -105,10 +109,10 @@ static int do_lstat( * the length of the path pointed to, which we expect everywhere else */ if (S_ISLNK(fMode)) { - char target[GIT_WIN_PATH]; + git_win32_path_as_utf8 target; int readlink_result; - readlink_result = p_readlink(file_name, target, GIT_WIN_PATH); + readlink_result = p_readlink(file_name, target, sizeof(target)); if (readlink_result == -1) return -1; @@ -121,8 +125,8 @@ static int do_lstat( errno = ENOENT; - /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the - * file path is a regular file,otherwise ENOENT must be set. + /* 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) { /* scan up path until we find an existing item */ @@ -156,13 +160,22 @@ int p_lstat_posixly(const char *filename, struct stat *buf) return do_lstat(filename, buf, 1); } + +/* + * Parts of the The p_readlink function are heavily inspired by the php + * readlink function in link_win32.c + * + * Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. + * + * For details of the PHP license see http://www.php.net/license/3_01.txt + */ int p_readlink(const char *link, char *target, size_t target_len) { typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD); static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - wchar_t link_w[GIT_WIN_PATH]; + git_win32_path link_w; wchar_t* target_w; int error = 0; @@ -185,7 +198,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - git__utf8_to_16(link_w, GIT_WIN_PATH, link); + git_win32_path_from_c(link_w, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -251,10 +264,10 @@ int p_symlink(const char *old, const char *new) int p_open(const char *path, int flags, ...) { - wchar_t buf[GIT_WIN_PATH]; + git_win32_path buf; mode_t mode = 0; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path_from_c(buf, path); if (flags & O_CREAT) { va_list arg_list; @@ -269,8 +282,8 @@ int p_open(const char *path, int flags, ...) int p_creat(const char *path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } @@ -296,7 +309,7 @@ int p_getcwd(char *buffer_out, size_t size) int p_stat(const char* path, struct stat* buf) { - char target[GIT_WIN_PATH]; + git_win32_path_as_utf8 target; int error = 0; error = do_lstat(path, buf, 0); @@ -304,7 +317,7 @@ int p_stat(const char* path, struct stat* buf) /* We need not do this in a loop to unwind chains of symlinks since * p_readlink calls GetFinalPathNameByHandle which does it for us. */ if (error >= 0 && S_ISLNK(buf->st_mode) && - (error = p_readlink(path, target, GIT_WIN_PATH)) >= 0) + (error = p_readlink(path, target, sizeof(target))) >= 0) error = do_lstat(target, buf, 0); return error; @@ -312,23 +325,23 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); return _wchmod(buf, mode); } int p_rmdir(const char* path) { int error; - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); error = _wrmdir(buf); @@ -344,24 +357,24 @@ int p_rmdir(const char* path) int p_hide_directory__w32(const char *path) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) { int ret; - wchar_t orig_path_w[GIT_WIN_PATH]; - wchar_t buffer_w[GIT_WIN_PATH]; + git_win32_path orig_path_w; + git_win32_path buffer_w; - git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path); + git_win32_path_from_c(orig_path_w, orig_path); /* Implicitly use GetCurrentDirectory which can be a threading issue */ - ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL); + ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL); /* According to MSDN, a return value equals to zero means a failure. */ - if (ret == 0 || ret > GIT_WIN_PATH) + if (ret == 0 || ret > GIT_WIN_PATH_UTF16) buffer = NULL; else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { @@ -445,18 +458,18 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - wchar_t buf[GIT_WIN_PATH]; - git__utf8_to_16(buf, GIT_WIN_PATH, path); + git_win32_path buf; + git_win32_path_from_c(buf, path); return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - wchar_t wfrom[GIT_WIN_PATH]; - wchar_t wto[GIT_WIN_PATH]; + git_win32_path wfrom; + git_win32_path wto; - git__utf8_to_16(wfrom, GIT_WIN_PATH, from); - git__utf8_to_16(wto, GIT_WIN_PATH, to); + git_win32_path_from_c(wfrom, from); + git_win32_path_from_c(wto, to); return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } @@ -505,94 +518,40 @@ p_gmtime_r (const time_t *timer, struct tm *result) return result; } -#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) -#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 -#else -#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL -#endif - -#ifndef _TIMEZONE_DEFINED -#define _TIMEZONE_DEFINED -struct timezone +int p_inet_pton(int af, const char *src, void *dst) { - int tz_minuteswest; /* minutes W of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; -#endif + struct sockaddr_storage sin; + void *addr; + int sin_len = sizeof(struct sockaddr_storage), addr_len; + int error = 0; -int p_gettimeofday(struct timeval *tv, struct timezone *tz) -{ - FILETIME ft; - unsigned __int64 tmpres = 0; - static int tzflag; - - if (NULL != tv) - { - GetSystemTimeAsFileTime(&ft); - - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - - /*converting file time to unix epoch*/ - tmpres /= 10; /*convert into microseconds*/ - tmpres -= DELTA_EPOCH_IN_MICROSECS; - tv->tv_sec = (long)(tmpres / 1000000UL); - tv->tv_usec = (long)(tmpres % 1000000UL); - } - - if (NULL != tz) - { - if (!tzflag) - { - _tzset(); - tzflag++; - } - tz->tz_minuteswest = _timezone / 60; - tz->tz_dsttime = _daylight; - } - - return 0; -} - -int p_inet_pton(int af, const char* src, void* dst) -{ - union { - struct sockaddr_in6 sin6; - struct sockaddr_in sin; - } sa; - int srcsize; - - switch(af) - { - case AF_INET: - sa.sin.sin_family = AF_INET; - srcsize = (int)sizeof(sa.sin); - break; - case AF_INET6: - sa.sin6.sin6_family = AF_INET6; - srcsize = (int)sizeof(sa.sin6); - break; - default: - errno = WSAEPFNOSUPPORT; - return -1; - } - - if (WSAStringToAddress((LPSTR)src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0) - { - errno = WSAGetLastError(); + if (af == AF_INET) { + addr = &((struct sockaddr_in *)&sin)->sin_addr; + addr_len = sizeof(struct in_addr); + } else if (af == AF_INET6) { + addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; + addr_len = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; return -1; } - switch(af) - { - case AF_INET: - memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr)); - break; - case AF_INET6: - memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr)); - break; + if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { + memcpy(dst, addr, addr_len); + return 1; } - return 1; + switch(WSAGetLastError()) { + case WSAEINVAL: + return 0; + case WSAEFAULT: + errno = ENOSPC; + return -1; + case WSA_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return -1; + } + + errno = EINVAL; + return -1; } diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h index 5de7e6f34..cbfe98812 100644 --- a/src/win32/precompiled.h +++ b/src/win32/precompiled.h @@ -1,4 +1,5 @@ #include "git2.h" +#include "common.h" #include #include @@ -6,6 +7,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 2f263b3e0..db8927471 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -6,6 +6,7 @@ */ #include "pthread.h" +#include "../global.h" int pthread_create( pthread_t *GIT_RESTRICT thread, @@ -127,9 +128,10 @@ int pthread_cond_signal(pthread_cond_t *cond) return 0; } -/* pthread_cond_broadcast is not implemented because doing so with just Win32 events - * is quite complicated, and no caller in libgit2 uses it yet. */ - +/* pthread_cond_broadcast is not implemented because doing so with just + * Win32 events is quite complicated, and no caller in libgit2 uses it + * yet. + */ int pthread_num_processors_np(void) { DWORD_PTR p, s; @@ -142,3 +144,111 @@ int pthread_num_processors_np(void) return n ? n : 1; } + +static HINSTANCE win32_kernel32_dll; + +typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); + +static win32_srwlock_fn win32_srwlock_initialize; +static win32_srwlock_fn win32_srwlock_acquire_shared; +static win32_srwlock_fn win32_srwlock_release_shared; +static win32_srwlock_fn win32_srwlock_acquire_exclusive; +static win32_srwlock_fn win32_srwlock_release_exclusive; + +int pthread_rwlock_init( + pthread_rwlock_t *GIT_RESTRICT lock, + const pthread_rwlockattr_t *GIT_RESTRICT attr) +{ + (void)attr; + + if (win32_srwlock_initialize) + win32_srwlock_initialize(&lock->native.srwl); + else + InitializeCriticalSection(&lock->native.csec); + + return 0; +} + +int pthread_rwlock_rdlock(pthread_rwlock_t *lock) +{ + if (win32_srwlock_acquire_shared) + win32_srwlock_acquire_shared(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int pthread_rwlock_rdunlock(pthread_rwlock_t *lock) +{ + if (win32_srwlock_release_shared) + win32_srwlock_release_shared(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int pthread_rwlock_wrlock(pthread_rwlock_t *lock) +{ + if (win32_srwlock_acquire_exclusive) + win32_srwlock_acquire_exclusive(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int pthread_rwlock_wrunlock(pthread_rwlock_t *lock) +{ + if (win32_srwlock_release_exclusive) + win32_srwlock_release_exclusive(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int pthread_rwlock_destroy(pthread_rwlock_t *lock) +{ + if (!win32_srwlock_initialize) + DeleteCriticalSection(&lock->native.csec); + git__memzero(lock, sizeof(*lock)); + return 0; +} + + +static void win32_pthread_shutdown(void) +{ + if (win32_kernel32_dll) { + FreeLibrary(win32_kernel32_dll); + win32_kernel32_dll = NULL; + } +} + +int win32_pthread_initialize(void) +{ + if (win32_kernel32_dll) + return 0; + + win32_kernel32_dll = LoadLibrary("Kernel32.dll"); + if (!win32_kernel32_dll) { + giterr_set(GITERR_OS, "Could not load Kernel32.dll!"); + return -1; + } + + win32_srwlock_initialize = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn) + GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive"); + + git__on_shutdown(win32_pthread_shutdown); + + return 0; +} diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 8277ecf6e..af5b121f0 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -19,22 +19,34 @@ typedef int pthread_mutexattr_t; typedef int pthread_condattr_t; typedef int pthread_attr_t; +typedef int pthread_rwlockattr_t; + typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; -#define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; +typedef struct { void *Ptr; } GIT_SRWLOCK; + +typedef struct { + union { + GIT_SRWLOCK srwl; + CRITICAL_SECTION csec; + } native; +} pthread_rwlock_t; + +#define PTHREAD_MUTEX_INITIALIZER {(void*)-1} int pthread_create( - pthread_t *GIT_RESTRICT, - const pthread_attr_t *GIT_RESTRICT, + pthread_t *GIT_RESTRICT thread, + const pthread_attr_t *GIT_RESTRICT attr, void *(*start_routine)(void*), - void *__restrict); + void *GIT_RESTRICT arg); int pthread_join(pthread_t, void **); int pthread_mutex_init( - pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT); + pthread_mutex_t *GIT_RESTRICT mutex, + const pthread_mutexattr_t *GIT_RESTRICT mutexattr); int pthread_mutex_destroy(pthread_mutex_t *); int pthread_mutex_lock(pthread_mutex_t *); int pthread_mutex_unlock(pthread_mutex_t *); @@ -47,4 +59,15 @@ int pthread_cond_signal(pthread_cond_t *); int pthread_num_processors_np(void); +int pthread_rwlock_init( + pthread_rwlock_t *GIT_RESTRICT lock, + const pthread_rwlockattr_t *GIT_RESTRICT attr); +int pthread_rwlock_rdlock(pthread_rwlock_t *); +int pthread_rwlock_rdunlock(pthread_rwlock_t *); +int pthread_rwlock_wrlock(pthread_rwlock_t *); +int pthread_rwlock_wrunlock(pthread_rwlock_t *); +int pthread_rwlock_destroy(pthread_rwlock_t *); + +extern int win32_pthread_initialize(void); + #endif diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index c06f3a8c2..d4dbfbab9 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) } #endif -int git__utf8_to_16(wchar_t *dest, size_t length, const char *src) +int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src) { - return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); + return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)dest_size); } -int git__utf16_to_8(char *out, const wchar_t *input) +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) { - return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, src, -1, dest, (int)dest_size, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 6cc9205f7..3af77580e 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -4,16 +4,35 @@ * 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 - #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ -#define GIT_WIN_PATH (260 + 1) +#include +#include "common.h" -int git__utf8_to_16(wchar_t *dest, size_t length, const char *src); -int git__utf16_to_8(char *dest, const wchar_t *src); +/* Maximum characters in a Windows path plus one for NUL byte */ +#define GIT_WIN_PATH_UTF16 (260 + 1) + +/* Maximum bytes necessary to convert a full-length UTF16 path to UTF8 */ +#define GIT_WIN_PATH_UTF8 (260 * 4 + 1) + +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; + +typedef char git_win32_path_as_utf8[GIT_WIN_PATH_UTF8]; + +/* dest_size is the size of dest in wchar_t's */ +int git__utf8_to_16(wchar_t * dest, size_t dest_size, const char *src); +/* dest_size is the size of dest in char's */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); + +GIT_INLINE(int) git_win32_path_from_c(git_win32_path dest, const char *src) +{ + return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src); +} + +GIT_INLINE(int) git_win32_path_to_c(git_win32_path_as_utf8 dest, const wchar_t *src) +{ + return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src); +} #endif - diff --git a/src/win32/version.h b/src/win32/version.h index 483962b57..79667697f 100644 --- a/src/win32/version.h +++ b/src/win32/version.h @@ -9,12 +9,29 @@ #include -GIT_INLINE(int) git_has_win32_version(int major, int minor) +GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) { - WORD wVersion = LOWORD(GetVersion()); + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = major; + version_test.dwMinorVersion = minor; + version_test.wServicePackMajor = (WORD)service_pack; + version_test.wServicePackMinor = 0; - return LOBYTE(wVersion) > major || - (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor); + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return 0; + + return 1; } #endif diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c deleted file mode 100644 index 0bda6bcec..000000000 --- a/tests-clar/config/multivar.c +++ /dev/null @@ -1,165 +0,0 @@ -#include "clar_libgit2.h" - -static const char *_name = "remote.fancy.url"; - -void test_config_multivar__initialize(void) -{ - cl_fixture_sandbox("config"); -} - -void test_config_multivar__cleanup(void) -{ - cl_fixture_cleanup("config"); -} - -static int mv_read_cb(const git_config_entry *entry, void *data) -{ - int *n = (int *) data; - - if (!strcmp(entry->name, _name)) - (*n)++; - - return 0; -} - -void test_config_multivar__foreach(void) -{ - git_config *cfg; - int n = 0; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11"))); - - cl_git_pass(git_config_foreach(cfg, mv_read_cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); -} - -static int cb(const git_config_entry *entry, void *data) -{ - int *n = (int *) data; - - GIT_UNUSED(entry); - - (*n)++; - - return 0; -} - -void test_config_multivar__get(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "example", cb, &n)); - cl_assert(n == 1); - - git_config_free(cfg); -} - -void test_config_multivar__add(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - cl_git_pass(git_config_set_multivar(cfg, _name, "nonexistant", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 3); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 1); - - git_config_free(cfg); - - /* We know it works in memory, let's see if the file is written correctly */ - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 3); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 1); - - git_config_free(cfg); -} - -void test_config_multivar__add_new(void) -{ - const char *var = "a.brand.new"; - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - cl_git_pass(git_config_set_multivar(cfg, var, "", "variable")); - n = 0; - cl_git_pass(git_config_get_multivar(cfg, var, NULL, cb, &n)); - cl_assert(n == 1); - - git_config_free(cfg); -} - -void test_config_multivar__replace(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); -} - -void test_config_multivar__replace_multiple(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar(cfg, _name, "otherplace", cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); -} diff --git a/tests-clar/diff/submodules.c b/tests-clar/diff/submodules.c deleted file mode 100644 index 6e52a6319..000000000 --- a/tests-clar/diff/submodules.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "posix.h" -#include "../submodule/submodule_helpers.h" - -static git_repository *g_repo = NULL; - -static void setup_submodules(void) -{ - g_repo = cl_git_sandbox_init("submodules"); - cl_fixture_sandbox("testrepo.git"); - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); -} - -static void setup_submodules2(void) -{ - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); -} - -void test_diff_submodules__initialize(void) -{ -} - -void test_diff_submodules__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - cl_fixture_cleanup("testrepo.git"); - cl_fixture_cleanup("submod2_target"); -} - -static void check_diff_patches(git_diff_list *diff, const char **expected) -{ - const git_diff_delta *delta; - git_diff_patch *patch = NULL; - size_t d, num_d = git_diff_num_deltas(diff); - char *patch_text; - - for (d = 0; d < num_d; ++d, git_diff_patch_free(patch)) { - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - - if (delta->status == GIT_DELTA_UNMODIFIED) - continue; - - if (expected[d] && !strcmp(expected[d], "")) - continue; - if (expected[d] && !strcmp(expected[d], "")) - cl_assert(0); - - cl_git_pass(git_diff_patch_to_str(&patch_text, patch)); - - cl_assert_equal_s(expected[d], patch_text); - git__free(patch_text); - } - - cl_assert(expected[d] && !strcmp(expected[d], "")); -} - -void test_diff_submodules__unmodified_submodule(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - NULL, /* added */ - NULL, /* ignored */ - "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ - NULL, /* testrepo.git */ - NULL, /* unmodified */ - NULL, /* untracked */ - "" - }; - - setup_submodules(); - - opts.flags = GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_list_free(diff); -} - -void test_diff_submodules__dirty_submodule(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - NULL, /* added */ - NULL, /* ignored */ - "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ - "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ - NULL, /* unmodified */ - NULL, /* untracked */ - "" - }; - - setup_submodules(); - - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); - - opts.flags = GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_list_free(diff); -} - -void test_diff_submodules__submod2_index_to_wd(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - NULL, /* not-submodule */ - NULL, /* not */ - "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ - "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ - "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ - "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ - "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ - "" - }; - - setup_submodules2(); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_list_free(diff); -} - -void test_diff_submodules__submod2_head_to_index(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_tree *head; - git_diff_list *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - "diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */ - "" - }; - - setup_submodules2(); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_list_free(diff); - - git_tree_free(head); -} diff --git a/tests-clar/network/urlparse.c b/tests-clar/network/urlparse.c deleted file mode 100644 index 173e57d0f..000000000 --- a/tests-clar/network/urlparse.c +++ /dev/null @@ -1,82 +0,0 @@ -#include "clar_libgit2.h" -#include "netops.h" - -char *host, *port, *user, *pass; - -void test_network_urlparse__initialize(void) -{ - host = port = user = pass = NULL; -} - -void test_network_urlparse__cleanup(void) -{ -#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; } - FREE_AND_NULL(host); - FREE_AND_NULL(port); - FREE_AND_NULL(user); - FREE_AND_NULL(pass); -} - -void test_network_urlparse__trivial(void) -{ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); -} - -void test_network_urlparse__user(void) -{ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "user@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); -} - -void test_network_urlparse__user_pass(void) -{ - /* user:pass@hostname.tld/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "user:pass@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); -} - -void test_network_urlparse__port(void) -{ - /* hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); -} - -void test_network_urlparse__user_port(void) -{ - /* user@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "user@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); -} - -void test_network_urlparse__user_pass_port(void) -{ - /* user:pass@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass, - "user:pass@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); -} diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c deleted file mode 100644 index 042bddab7..000000000 --- a/tests-clar/object/blob/filter.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "blob.h" -#include "filter.h" -#include "buf_text.h" - -static git_repository *g_repo = NULL; -#define NUM_TEST_OBJECTS 8 -static git_oid g_oids[NUM_TEST_OBJECTS]; -static const char *g_raw[NUM_TEST_OBJECTS] = { - "", - "foo\nbar\n", - "foo\rbar\r", - "foo\r\nbar\r\n", - "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", - "123\n\000\001\002\003\004abc\255\254\253\r\n", - "\xEF\xBB\xBFThis is UTF-8\n", - "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" -}; -static git_off_t g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, 12 }; -static git_buf_text_stats g_stats[NUM_TEST_OBJECTS] = { - { 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 2, 0, 6, 0 }, - { 0, 0, 2, 0, 0, 6, 0 }, - { 0, 0, 2, 2, 2, 6, 0 }, - { 0, 0, 4, 4, 1, 31, 0 }, - { 0, 1, 1, 2, 1, 9, 5 }, - { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, - { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, -}; -static git_buf g_crlf_filtered[NUM_TEST_OBJECTS] = { - { "", 0, 0 }, - { "foo\nbar\n", 0, 8 }, - { "foo\rbar\r", 0, 8 }, - { "foo\nbar\n", 0, 8 }, - { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, - { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, - { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } -}; - -void test_object_blob_filter__initialize(void) -{ - int i; - - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(p_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); - cl_git_pass(git_repository_open(&g_repo, "empty_standard_repo")); - - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - size_t len = (g_len[i] < 0) ? strlen(g_raw[i]) : (size_t)g_len[i]; - g_len[i] = (git_off_t)len; - - cl_git_pass( - git_blob_create_frombuffer(&g_oids[i], g_repo, g_raw[i], len) - ); - } -} - -void test_object_blob_filter__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; - cl_fixture_cleanup("empty_standard_repo"); -} - -void test_object_blob_filter__unfiltered(void) -{ - int i; - git_blob *blob; - - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); - cl_assert(g_len[i] == git_blob_rawsize(blob)); - cl_assert(memcmp(git_blob_rawcontent(blob), g_raw[i], (size_t)g_len[i]) == 0); - git_blob_free(blob); - } -} - -void test_object_blob_filter__stats(void) -{ - int i; - git_blob *blob; - git_buf buf = GIT_BUF_INIT; - git_buf_text_stats stats; - - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); - cl_git_pass(git_blob__getbuf(&buf, blob)); - git_buf_text_gather_stats(&stats, &buf, false); - cl_assert(memcmp(&g_stats[i], &stats, sizeof(stats)) == 0); - git_blob_free(blob); - } - - git_buf_free(&buf); -} - -void test_object_blob_filter__to_odb(void) -{ - git_vector filters = GIT_VECTOR_INIT; - git_config *cfg; - int i; - git_blob *blob; - git_buf orig = GIT_BUF_INIT, out = GIT_BUF_INIT; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_assert(cfg); - - git_attr_cache_flush(g_repo); - cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); - - cl_assert(git_filters_load( - &filters, g_repo, "filename.txt", GIT_FILTER_TO_ODB) > 0); - cl_assert(filters.length == 1); - - for (i = 0; i < NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_oids[i])); - cl_git_pass(git_blob__getbuf(&orig, blob)); - - cl_git_pass(git_filters_apply(&out, &orig, &filters)); - cl_assert(git_buf_cmp(&out, &g_crlf_filtered[i]) == 0); - - git_blob_free(blob); - } - - git_filters_free(&filters); - git_buf_free(&orig); - git_buf_free(&out); - git_config_free(cfg); -} - diff --git a/tests-clar/odb/mixed.c b/tests-clar/odb/mixed.c deleted file mode 100644 index da0ed97d7..000000000 --- a/tests-clar/odb/mixed.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" - -static git_odb *_odb; - -void test_odb_mixed__initialize(void) -{ - cl_git_pass(git_odb_open(&_odb, cl_fixture("duplicate.git/objects"))); -} - -void test_odb_mixed__cleanup(void) -{ - git_odb_free(_odb); - _odb = NULL; -} - -void test_odb_mixed__dup_oid(void) { - const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; - git_oid oid; - git_odb_object *obj; - cl_git_pass(git_oid_fromstr(&oid, hex)); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); - git_odb_object_free(obj); -} - diff --git a/tests-clar/stash/stash_helpers.h b/tests-clar/stash/stash_helpers.h deleted file mode 100644 index bb7fec4f5..000000000 --- a/tests-clar/stash/stash_helpers.h +++ /dev/null @@ -1,8 +0,0 @@ -void setup_stash( - git_repository *repo, - git_signature *signature); - -void commit_staged_files( - git_oid *commit_oid, - git_index *index, - git_signature *signature); \ No newline at end of file diff --git a/tests-clar/submodule/submodule_helpers.h b/tests-clar/submodule/submodule_helpers.h deleted file mode 100644 index 6b76a832e..000000000 --- a/tests-clar/submodule/submodule_helpers.h +++ /dev/null @@ -1,2 +0,0 @@ -extern void rewrite_gitmodules(const char *workdir); - diff --git a/tests-clar/README.md b/tests/README.md similarity index 100% rename from tests-clar/README.md rename to tests/README.md diff --git a/tests-clar/attr/attr_expect.h b/tests/attr/attr_expect.h similarity index 100% rename from tests-clar/attr/attr_expect.h rename to tests/attr/attr_expect.h diff --git a/tests-clar/attr/file.c b/tests/attr/file.c similarity index 96% rename from tests-clar/attr/file.c rename to tests/attr/file.c index 8866fd9bd..4eb1d22fe 100644 --- a/tests-clar/attr/file.c +++ b/tests/attr/file.c @@ -49,7 +49,6 @@ void test_attr_file__match_variants(void) cl_assert(rule); cl_assert_equal_s("pat0", rule->match.pattern); cl_assert(rule->match.length == strlen("pat0")); - cl_assert(rule->match.flags == 0); cl_assert(rule->assigns.length == 1); assign = get_assign(rule,0); cl_assert_equal_s("attr0", assign->name); @@ -59,16 +58,16 @@ void test_attr_file__match_variants(void) rule = get_rule(1); cl_assert_equal_s("pat1", rule->match.pattern); cl_assert(rule->match.length == strlen("pat1")); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_NEGATIVE); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); rule = get_rule(2); cl_assert_equal_s("pat2", rule->match.pattern); cl_assert(rule->match.length == strlen("pat2")); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); rule = get_rule(3); cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); - cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); rule = get_rule(4); cl_assert_equal_s("pat4.*", rule->match.pattern); @@ -89,7 +88,6 @@ void test_attr_file__match_variants(void) rule = get_rule(8); cl_assert_equal_s("pat8 with spaces", rule->match.pattern); cl_assert(rule->match.length == strlen("pat8 with spaces")); - cl_assert(rule->match.flags == 0); rule = get_rule(9); cl_assert_equal_s("pat9", rule->match.pattern); diff --git a/tests-clar/attr/flags.c b/tests/attr/flags.c similarity index 100% rename from tests-clar/attr/flags.c rename to tests/attr/flags.c diff --git a/tests-clar/attr/ignore.c b/tests/attr/ignore.c similarity index 100% rename from tests-clar/attr/ignore.c rename to tests/attr/ignore.c diff --git a/tests-clar/attr/lookup.c b/tests/attr/lookup.c similarity index 100% rename from tests-clar/attr/lookup.c rename to tests/attr/lookup.c diff --git a/tests-clar/attr/repo.c b/tests/attr/repo.c similarity index 94% rename from tests-clar/attr/repo.c rename to tests/attr/repo.c index ca3e71e7f..ef2ad5ce9 100644 --- a/tests-clar/attr/repo.c +++ b/tests/attr/repo.c @@ -100,6 +100,22 @@ void test_attr_repo__get_many(void) cl_assert_equal_s("yes", values[3]); } +void test_attr_repo__get_many_in_place(void) +{ + const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; + + /* it should be legal to look up values into the same array that has + * the attribute names, overwriting each name as the value is found. + */ + + cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals)); + + cl_assert(GIT_ATTR_TRUE(vals[0])); + cl_assert(GIT_ATTR_TRUE(vals[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(vals[2])); + cl_assert_equal_s("yes", vals[3]); +} + static int count_attrs( const char *name, const char *value, diff --git a/tests/blame/blame_helpers.c b/tests/blame/blame_helpers.c new file mode 100644 index 000000000..d64bb5c4c --- /dev/null +++ b/tests/blame/blame_helpers.c @@ -0,0 +1,64 @@ +#include "blame_helpers.h" + +void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) +{ + va_list arglist; + + printf("Hunk %zd (line %d +%d): ", idx, + hunk->final_start_line_number, hunk->lines_in_hunk-1); + + va_start(arglist, fmt); + vprintf(fmt, arglist); + va_end(arglist); + + printf("\n"); +} + +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) +{ + char expected[41] = {0}, actual[41] = {0}; + const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx); + cl_assert(hunk); + + if (!strncmp(commit_id, "0000", 4)) { + strcpy(expected, "0000000000000000000000000000000000000000"); + } else { + git_object *obj; + cl_git_pass(git_revparse_single(&obj, repo, commit_id)); + git_oid_fmt(expected, git_object_id(obj)); + git_object_free(obj); + } + + if (hunk->final_start_line_number != start_line) { + hunk_message(idx, hunk, "mismatched start line number: expected %d, got %d", + start_line, hunk->final_start_line_number); + } + cl_assert_equal_i(hunk->final_start_line_number, start_line); + + if (hunk->lines_in_hunk != len) { + hunk_message(idx, hunk, "mismatched line count: expected %d, got %d", + len, hunk->lines_in_hunk); + } + cl_assert_equal_i(hunk->lines_in_hunk, len); + + git_oid_fmt(actual, &hunk->final_commit_id); + if (strcmp(expected, actual)) { + hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n", + actual, expected); + } + cl_assert_equal_s(actual, expected); + if (strcmp(hunk->orig_path, orig_path)) { + hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n", + hunk->orig_path, orig_path); + } + cl_assert_equal_s(hunk->orig_path, orig_path); + + if (hunk->boundary != boundary) { + hunk_message(idx, hunk, "doesn't match boundary flag (got %d, expected %d)\n", + hunk->boundary, boundary); + } + cl_assert_equal_i(boundary, hunk->boundary); +} + + diff --git a/tests/blame/blame_helpers.h b/tests/blame/blame_helpers.h new file mode 100644 index 000000000..94321a5b5 --- /dev/null +++ b/tests/blame/blame_helpers.h @@ -0,0 +1,16 @@ +#include "clar_libgit2.h" +#include "blame.h" + +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); + + diff --git a/tests/blame/buffer.c b/tests/blame/buffer.c new file mode 100644 index 000000000..912ee9846 --- /dev/null +++ b/tests/blame/buffer.c @@ -0,0 +1,166 @@ +#include "blame_helpers.h" + +git_repository *g_repo; +git_blame *g_fileblame, *g_bufferblame; + +void test_blame_buffer__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL)); + g_bufferblame = NULL; +} + +void test_blame_buffer__cleanup(void) +{ + git_blame_free(g_fileblame); + git_blame_free(g_bufferblame); + git_repository_free(g_repo); +} + +void test_blame_buffer__added_line(void) +{ + const git_blame_hunk *hunk; + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +abcdefg\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt"); + + hunk = git_blame_get_hunk_byline(g_bufferblame, 16); + cl_assert(hunk); + cl_assert_equal_s("Ben Straub", hunk->final_signature->name); +} + +void test_blame_buffer__deleted_line(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__add_splits_hunk(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, 0, "63d671eb", "b.txt"); +} + +void test_blame_buffer__delete_crosses_hunk_boundary(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__replace_line(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, 0, "63d671eb", "b.txt"); +} + +void test_blame_buffer__add_lines_at_end(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +abc\n\ +def\n"; + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + + cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 16, 2, 0, "00000000", "b.txt"); +} diff --git a/tests/blame/getters.c b/tests/blame/getters.c new file mode 100644 index 000000000..66eaeecf9 --- /dev/null +++ b/tests/blame/getters.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" + +#include "blame.h" + +git_blame *g_blame; + +void test_blame_getters__initialize(void) +{ + size_t i; + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + git_blame_hunk hunks[] = { + { 3, {{0}}, 1, NULL, {{0}}, "a", 0}, + { 3, {{0}}, 4, NULL, {{0}}, "b", 0}, + { 3, {{0}}, 7, NULL, {{0}}, "c", 0}, + { 3, {{0}}, 10, NULL, {{0}}, "d", 0}, + { 3, {{0}}, 13, NULL, {{0}}, "e", 0}, + }; + + g_blame = git_blame__alloc(NULL, opts, ""); + + for (i=0; i<5; i++) { + git_blame_hunk *h = git__calloc(1, sizeof(git_blame_hunk)); + h->final_start_line_number = hunks[i].final_start_line_number; + h->orig_path = git__strdup(hunks[i].orig_path); + h->lines_in_hunk = hunks[i].lines_in_hunk; + + git_vector_insert(&g_blame->hunks, h); + } +} + +void test_blame_getters__cleanup(void) +{ + git_blame_free(g_blame); +} + + +void test_blame_getters__byindex(void) +{ + const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2); + cl_assert(h); + cl_assert_equal_s(h->orig_path, "c"); + + h = git_blame_get_hunk_byindex(g_blame, 95); + cl_assert_equal_p(h, NULL); +} + +void test_blame_getters__byline(void) +{ + const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5); + cl_assert(h); + cl_assert_equal_s(h->orig_path, "b"); + + h = git_blame_get_hunk_byline(g_blame, 95); + cl_assert_equal_p(h, NULL); +} diff --git a/tests/blame/harder.c b/tests/blame/harder.c new file mode 100644 index 000000000..7c4dd4f74 --- /dev/null +++ b/tests/blame/harder.c @@ -0,0 +1,71 @@ +#include "clar_libgit2.h" + +#include "blame.h" + + +/** + * The test repo has a history that looks like this: + * + * * (A) bc7c5ac + * |\ + * | * (B) aa06ecc + * * | (C) 63d671e + * |/ + * * (D) da23739 + * * (E) b99f7ac + * + */ + +static git_repository *g_repo = NULL; + +void test_blame_harder__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); +} + +void test_blame_harder__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; +} + + + +void test_blame_harder__m(void) +{ + /* TODO */ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE; +} + + +void test_blame_harder__c(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + /* Attribute the first hunk in b.txt to (E), since it was cut/pasted from + * a.txt in (D). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; +} + +void test_blame_harder__cc(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + /* Attribute the second hunk in b.txt to (E), since it was copy/pasted from + * a.txt in (C). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; +} + +void test_blame_harder__ccc(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + /* Attribute the third hunk in b.txt to (E). This hunk was deleted from + * a.txt in (D), but reintroduced in (B). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES; +} diff --git a/tests/blame/simple.c b/tests/blame/simple.c new file mode 100644 index 000000000..79bd56b83 --- /dev/null +++ b/tests/blame/simple.c @@ -0,0 +1,305 @@ +#include "blame_helpers.h" + +static git_repository *g_repo; +static git_blame *g_blame; + +void test_blame_simple__initialize(void) +{ + g_repo = NULL; + g_blame = NULL; +} + +void test_blame_simple__cleanup(void) +{ + git_blame_free(g_blame); + git_repository_free(g_repo); +} + +/* + * $ git blame -s branch_file.txt + * orig line no final line no + * commit V author timestamp V + * c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1 + * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2 + */ +void test_blame_simple__trivial_testrepo(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted"))); + cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL)); + + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt"); +} + +/* + * $ git blame -n b.txt + * orig line no final line no + * commit V author timestamp V + * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 + * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 + * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 + * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 + * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 + * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 + * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 + * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 + * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 + */ +void test_blame_simple__trivial_blamerepo(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL)); + + cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "aa06ecca", "b.txt"); +} + + +/* + * $ git blame -n 359fc2d -- include/git2.h + * orig line no final line no + * commit orig path V author timestamp V + * d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1 + * 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2 + * d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3 + * bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4 + * bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5 + * d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6 + * d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7 + * d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8 + * d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9 + * d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10 + * 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11 + * 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12 + * 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13 + * a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14 + * 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15 + * d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16 + * 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17 + * d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18 + * 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19 + * 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20 + * 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21 + * d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22 + * 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23 + * 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24 + * bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25 + * 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26 + * 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27 + * 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28 + * a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29 + * d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30 + * 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31 + * 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32 + * 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33 + * 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34 + * 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35 + * 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36 + * d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37 + * 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38 + * 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39 + * 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40 + * 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41 + * 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42 + * 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43 + * 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44 + * d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45 + * 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46 + * f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47 + * 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48 + * 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49 + * d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50 + * b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51 + * 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52 + * bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53 + * bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54 + * edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55 + * 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56 + * 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57 + * 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58 + * bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59 + * d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60 + */ +void test_blame_simple__trivial_libgit2(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + git_object *obj; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("../.."))); + + /* This test can't work on a shallow clone */ + if (git_repository_is_shallow(g_repo)) + return; + + cl_git_pass(git_revparse_single(&obj, g_repo, "359fc2d")); + git_oid_cpy(&opts.newest_commit, git_object_id(obj)); + git_object_free(obj); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "include/git2.h", &opts)); + + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "359fc2d2", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 2, 3, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 3, 4, 2, 0, "bb742ede", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 4, 6, 5, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 5, 11, 1, 0, "96fab093", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 6, 12, 1, 0, "9d1dcca2", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 7, 13, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 8, 14, 1, 0, "a15c550d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 9, 15, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 10, 16, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 11, 17, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 12, 18, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 13, 19, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 14, 20, 1, 0, "638c2ca4", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 15, 21, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 16, 22, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 17, 23, 2, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 18, 25, 1, 0, "bf787bd8", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 19, 26, 1, 0, "0984c876", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 20, 27, 1, 0, "2f8a8ab2", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 21, 28, 1, 0, "27df4275", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 22, 29, 1, 0, "a346992f", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 23, 30, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 24, 31, 5, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 25, 36, 1, 0, "65b09b1d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 26, 37, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 27, 38, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 28, 39, 1, 0, "5d4cd003", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 29, 40, 1, 0, "41fb1ca0", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 30, 41, 1, 0, "2dc31040", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 31, 42, 1, 0, "764df57e", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 32, 43, 1, 0, "5280f4e6", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 33, 44, 1, 0, "613d5eb9", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 34, 45, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 35, 46, 1, 0, "111ee3fe", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 36, 47, 1, 0, "f004c4a8", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 37, 48, 1, 0, "111ee3fe", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 38, 49, 1, 0, "9c82357b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 39, 50, 1, 0, "d6258deb", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 40, 51, 1, 0, "b311e313", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 41, 52, 1, 0, "3412391d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 42, 53, 1, 0, "bfc9ca59", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 43, 54, 1, 0, "bf477ed4", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 44, 55, 1, 0, "edebceff", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 45, 56, 1, 0, "743a4b3b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 46, 57, 1, 0, "0a32dca5", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 47, 58, 1, 0, "590fb68b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 48, 59, 1, 0, "bf477ed4", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h"); +} + + +/* + * $ git blame -n b.txt -L 8 + * orig line no final line no + * commit V author timestamp V + * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 + * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 + * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 + * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 + * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 + * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 + * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 + * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 + */ +void test_blame_simple__can_restrict_lines_min(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.min_line = 8; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt"); +} + +/* + * $ git blame -n b.txt -L ,6 + * orig line no final line no + * commit V author timestamp V + * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + */ +void test_blame_simple__can_restrict_lines_max(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.max_line = 6; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt"); +} + +/* + * $ git blame -n b.txt -L 2,7 + * orig line no final line no + * commit V author timestamp V + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 + */ +void test_blame_simple__can_restrict_lines_both(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.min_line = 2; + opts.max_line = 7; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt"); +} + +/* + * $ git blame -n branch_file.txt be3563a..HEAD + * orig line no final line no + * commit V author timestamp V + * ^be3563a 1 (Scott Chacon 2010-05-25 11:58:27 -0700 1) hi + * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2) bye! + */ +void test_blame_simple__can_restrict_to_newish_commits(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + + { + git_object *obj; + cl_git_pass(git_revparse_single(&obj, g_repo, "be3563a")); + git_oid_cpy(&opts.oldest_commit, git_object_id(obj)); + git_object_free(obj); + } + + cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts)); + + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt"); +} diff --git a/tests-clar/buf/basic.c b/tests/buf/basic.c similarity index 100% rename from tests-clar/buf/basic.c rename to tests/buf/basic.c diff --git a/tests-clar/buf/splice.c b/tests/buf/splice.c similarity index 100% rename from tests-clar/buf/splice.c rename to tests/buf/splice.c diff --git a/tests-clar/checkout/binaryunicode.c b/tests/checkout/binaryunicode.c similarity index 100% rename from tests-clar/checkout/binaryunicode.c rename to tests/checkout/binaryunicode.c diff --git a/tests-clar/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c similarity index 68% rename from tests-clar/checkout/checkout_helpers.c rename to tests/checkout/checkout_helpers.c index 8da024dda..06b4e0682 100644 --- a/tests-clar/checkout/checkout_helpers.c +++ b/tests/checkout/checkout_helpers.c @@ -3,22 +3,6 @@ #include "refs.h" #include "fileops.h" -/* this is essentially the code from git__unescape modified slightly */ -void strip_cr_from_buf(git_buf *buf) -{ - char *scan, *pos = buf->ptr, *end = pos + buf->size; - - for (scan = pos; scan < end; pos++, scan++) { - if (*scan == '\r') - scan++; /* skip '\r' */ - if (pos != scan) - *pos = *scan; - } - - *pos = '\0'; - buf->size = (pos - buf->ptr); -} - void assert_on_branch(git_repository *repo, const char *branch) { git_reference *head; @@ -50,48 +34,6 @@ void reset_index_to_treeish(git_object *treeish) git_index_free(index); } -static void check_file_contents_internal( - const char *path, - const char *expected_content, - bool strip_cr, - const char *file, - int line, - const char *msg) -{ - int fd; - char data[1024] = {0}; - git_buf buf = GIT_BUF_INIT; - size_t expected_len = expected_content ? strlen(expected_content) : 0; - - fd = p_open(path, O_RDONLY); - cl_assert(fd >= 0); - - buf.ptr = data; - buf.size = p_read(fd, buf.ptr, sizeof(data)); - - cl_git_pass(p_close(fd)); - - if (strip_cr) - strip_cr_from_buf(&buf); - - clar__assert_equal_i((int)expected_len, (int)buf.size, file, line, "strlen(expected_content) != strlen(actual_content)", 1); - clar__assert_equal_s(expected_content, buf.ptr, file, line, msg, 1); -} - -void check_file_contents_at_line( - const char *path, const char *expected, - const char *file, int line, const char *msg) -{ - check_file_contents_internal(path, expected, false, file, line, msg); -} - -void check_file_contents_nocr_at_line( - const char *path, const char *expected, - const char *file, int line, const char *msg) -{ - check_file_contents_internal(path, expected, true, file, line, msg); -} - int checkout_count_callback( git_checkout_notify_t why, const char *path, diff --git a/tests-clar/checkout/checkout_helpers.h b/tests/checkout/checkout_helpers.h similarity index 54% rename from tests-clar/checkout/checkout_helpers.h rename to tests/checkout/checkout_helpers.h index 0e8da31d1..705ee903d 100644 --- a/tests-clar/checkout/checkout_helpers.h +++ b/tests/checkout/checkout_helpers.h @@ -2,23 +2,14 @@ #include "git2/object.h" #include "git2/repository.h" -extern void strip_cr_from_buf(git_buf *buf); extern void assert_on_branch(git_repository *repo, const char *branch); extern void reset_index_to_treeish(git_object *treeish); -extern void check_file_contents_at_line( - const char *path, const char *expected, - const char *file, int line, const char *msg); - -extern void check_file_contents_nocr_at_line( - const char *path, const char *expected, - const char *file, int line, const char *msg); - #define check_file_contents(PATH,EXP) \ - check_file_contents_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) + cl_assert_equal_file(EXP,0,PATH) #define check_file_contents_nocr(PATH,EXP) \ - check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) + cl_assert_equal_file_ignore_cr(EXP,0,PATH) typedef struct { int n_conflicts; diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c new file mode 100644 index 000000000..66965a89b --- /dev/null +++ b/tests/checkout/conflict.c @@ -0,0 +1,1127 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/sys/index.h" +#include "fileops.h" + +static git_repository *g_repo; +static git_index *g_index; + +#define TEST_REPO_PATH "merge-resolve" + +#define CONFLICTING_ANCESTOR_OID "d427e0b2e138501a3d15cc376077a3631e15bd46" +#define CONFLICTING_OURS_OID "4e886e602529caa9ab11d71f86634bd1b6e0de10" +#define CONFLICTING_THEIRS_OID "2bd0a343aeef7a2cf0d158478966a6e587ff3863" + +#define AUTOMERGEABLE_ANCESTOR_OID "6212c31dab5e482247d7977e4f0dd3601decf13b" +#define AUTOMERGEABLE_OURS_OID "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf" +#define AUTOMERGEABLE_THEIRS_OID "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" + +#define LINK_ANCESTOR_OID "1a010b1c0f081b2e8901d55307a15c29ff30af0e" +#define LINK_OURS_OID "72ea499e108df5ff0a4a913e7655bbeeb1fb69f2" +#define LINK_THEIRS_OID "8bfb012a6d809e499bd8d3e194a3929bc8995b93" + +#define LINK_ANCESTOR_TARGET "file" +#define LINK_OURS_TARGET "other-file" +#define LINK_THEIRS_TARGET "still-another-file" + +#define CONFLICTING_OURS_FILE \ + "this file is changed in master and branch\n" +#define CONFLICTING_THEIRS_FILE \ + "this file is changed in branch and master\n" +#define CONFLICTING_DIFF3_FILE \ + "<<<<<<< ours\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> theirs\n" + +#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" + +struct checkout_index_entry { + uint16_t mode; + char oid_str[41]; + int stage; + char path[128]; +}; + +struct checkout_name_entry { + char ancestor[64]; + char ours[64]; + char theirs[64]; +}; + +void test_checkout_conflict__initialize(void) +{ + g_repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&g_index, g_repo); + + cl_git_rewritefile( + TEST_REPO_PATH "/.gitattributes", + "* text eol=lf\n"); +} + +void test_checkout_conflict__cleanup(void) +{ + git_index_free(g_index); + cl_git_sandbox_cleanup(); +} + +static void create_index(struct checkout_index_entry *entries, size_t entries_len) +{ + git_buf path = GIT_BUF_INIT; + size_t i; + + for (i = 0; i < entries_len; i++) { + git_buf_joinpath(&path, TEST_REPO_PATH, entries[i].path); + + if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2)) + p_unlink(git_buf_cstr(&path)); + + git_index_remove_bypath(g_index, entries[i].path); + } + + for (i = 0; i < entries_len; i++) { + git_index_entry entry; + + memset(&entry, 0x0, sizeof(git_index_entry)); + + entry.mode = entries[i].mode; + entry.flags = entries[i].stage << GIT_IDXENTRY_STAGESHIFT; + git_oid_fromstr(&entry.oid, entries[i].oid_str); + entry.path = entries[i].path; + + cl_git_pass(git_index_add(g_index, &entry)); + } + + git_buf_free(&path); +} + +static void create_index_names(struct checkout_name_entry *entries, size_t entries_len) +{ + size_t i; + + for (i = 0; i < entries_len; i++) { + cl_git_pass(git_index_name_add(g_index, + strlen(entries[i].ancestor) == 0 ? NULL : entries[i].ancestor, + strlen(entries[i].ours) == 0 ? NULL : entries[i].ours, + strlen(entries[i].theirs) == 0 ? NULL : entries[i].theirs)); + } +} + +static void create_conflicting_index(void) +{ + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, + }; + + create_index(checkout_index_entries, 3); + git_index_write(g_index); +} + +static void ensure_workdir_contents(const char *path, const char *contents) +{ + git_buf fullpath = GIT_BUF_INIT, data_buf = GIT_BUF_INIT; + + cl_git_pass( + git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(git_futils_readbuffer(&data_buf, git_buf_cstr(&fullpath))); + cl_assert(strcmp(git_buf_cstr(&data_buf), contents) == 0); + + git_buf_free(&fullpath); + git_buf_free(&data_buf); +} + +static void ensure_workdir_oid(const char *path, const char *oid_str) +{ + git_oid expected, actual; + + cl_git_pass(git_oid_fromstr(&expected, oid_str)); + cl_git_pass(git_repository_hashfile(&actual, g_repo, path, GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_cmp(&expected, &actual) == 0); +} + +static void ensure_workdir_mode(const char *path, int mode) +{ +#ifndef GIT_WIN32 + git_buf fullpath = GIT_BUF_INIT; + struct stat st; + + cl_git_pass( + git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(p_stat(git_buf_cstr(&fullpath), &st)); + cl_assert_equal_i(mode, st.st_mode); + + git_buf_free(&fullpath); +#endif +} + +static void ensure_workdir(const char *path, int mode, const char *oid_str) +{ + ensure_workdir_mode(path, mode); + ensure_workdir_oid(path, oid_str); +} + +static void ensure_workdir_link(const char *path, const char *target) +{ +#ifdef GIT_WIN32 + ensure_workdir_contents(path, target); +#else + git_buf fullpath = GIT_BUF_INIT; + char actual[1024]; + struct stat st; + int len; + + cl_git_pass( + git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(p_lstat(git_buf_cstr(&fullpath), &st)); + cl_assert(S_ISLNK(st.st_mode)); + + cl_assert((len = p_readlink(git_buf_cstr(&fullpath), actual, 1024)) > 0); + actual[len] = '\0'; + cl_assert(strcmp(actual, target) == 0); + + git_buf_free(&fullpath); +#endif +} + +void test_checkout_conflict__ignored(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED; + + create_conflicting_index(); + cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt")); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + cl_assert(!git_path_exists(TEST_REPO_PATH "/conflicting.txt")); +} + +void test_checkout_conflict__ours(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_OURS_FILE); +} + +void test_checkout_conflict__theirs(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_THEIRS_FILE); + +} + +void test_checkout_conflict__diff3(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); +} + +void test_checkout_conflict__automerge(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, + }; + + create_index(checkout_index_entries, 3); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); +} + +void test_checkout_conflict__directory_file(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, + { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, + + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, + + { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 12); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-1~ours", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2~theirs", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-3~theirs", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-4~ours", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__directory_file_with_custom_labels(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, + { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, + + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, + + { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + opts.our_label = "HEAD"; + opts.their_label = "branch"; + + create_index(checkout_index_entries, 12); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-1~HEAD", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2~branch", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-3~branch", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-4~HEAD", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__link_file(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "link-1" }, + { 0120000, LINK_THEIRS_OID, 3, "link-1" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-2" }, + { 0120000, LINK_OURS_OID, 2, "link-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "link-2" }, + + { 0120000, LINK_ANCESTOR_OID, 1, "link-3" }, + { 0100644, CONFLICTING_OURS_OID, 2, "link-3" }, + { 0120000, LINK_THEIRS_OID, 3, "link-3" }, + + { 0120000, LINK_ANCESTOR_OID, 1, "link-4" }, + { 0120000, LINK_OURS_OID, 2, "link-4" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 12); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Typechange conflicts always keep the file in the workdir */ + ensure_workdir_oid("link-1", CONFLICTING_OURS_OID); + ensure_workdir_oid("link-2", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("link-3", CONFLICTING_OURS_OID); + ensure_workdir_oid("link-4", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__links(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0120000, LINK_ANCESTOR_OID, 1, "link-1" }, + { 0120000, LINK_OURS_OID, 2, "link-1" }, + { 0120000, LINK_THEIRS_OID, 3, "link-1" }, + + { 0120000, LINK_OURS_OID, 2, "link-2" }, + { 0120000, LINK_THEIRS_OID, 3, "link-2" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 5); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Conflicts with links always keep the ours side (even with -Xtheirs) */ + ensure_workdir_link("link-1", LINK_OURS_TARGET); + ensure_workdir_link("link-2", LINK_OURS_TARGET); +} + +void test_checkout_conflict__add_add(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 2); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Add/add writes diff3 files */ + ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); +} + +void test_checkout_conflict__mode_change(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-1" }, + { 0100755, CONFLICTING_ANCESTOR_OID, 2, "executable-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-1" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-2" }, + { 0100644, CONFLICTING_OURS_OID, 2, "executable-2" }, + { 0100755, CONFLICTING_ANCESTOR_OID, 3, "executable-2" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 2, "executable-3" }, + { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-3" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-4" }, + { 0100755, CONFLICTING_OURS_OID, 2, "executable-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 3, "executable-4" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-5" }, + { 0100755, CONFLICTING_OURS_OID, 2, "executable-5" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-5" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-6" }, + { 0100644, CONFLICTING_OURS_OID, 2, "executable-6" }, + { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 18); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Keep the modified mode */ + ensure_workdir_oid("executable-1", CONFLICTING_THEIRS_OID); + ensure_workdir_mode("executable-1", 0100755); + + ensure_workdir_oid("executable-2", CONFLICTING_OURS_OID); + ensure_workdir_mode("executable-2", 0100755); + + ensure_workdir_oid("executable-3", CONFLICTING_THEIRS_OID); + ensure_workdir_mode("executable-3", 0100644); + + ensure_workdir_oid("executable-4", CONFLICTING_OURS_OID); + ensure_workdir_mode("executable-4", 0100644); + + ensure_workdir_contents("executable-5", CONFLICTING_DIFF3_FILE); + ensure_workdir_mode("executable-5", 0100755); + + ensure_workdir_contents("executable-6", CONFLICTING_DIFF3_FILE); + ensure_workdir_mode("executable-6", 0100644); +} + +void test_checkout_conflict__renames(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "3a-renamed-in-ours-deleted-in-theirs.txt", + "3a-newname-in-ours-deleted-in-theirs.txt", + "" + }, + + { + "3b-renamed-in-theirs-deleted-in-ours.txt", + "", + "3b-newname-in-theirs-deleted-in-ours.txt" + }, + + { + "4a-renamed-in-ours-added-in-theirs.txt", + "4a-newname-in-ours-added-in-theirs.txt", + "" + }, + + { + "4b-renamed-in-theirs-added-in-ours.txt", + "", + "4b-newname-in-theirs-added-in-ours.txt" + }, + + { + "5a-renamed-in-ours-added-in-theirs.txt", + "5a-newname-in-ours-added-in-theirs.txt", + "5a-renamed-in-ours-added-in-theirs.txt" + }, + + { + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-newname-in-theirs-added-in-ours.txt" + }, + + { + "6-both-renamed-1-to-2.txt", + "6-both-renamed-1-to-2-ours.txt", + "6-both-renamed-1-to-2-theirs.txt" + }, + + { + "7-both-renamed-side-1.txt", + "7-both-renamed.txt", + "7-both-renamed-side-1.txt" + }, + + { + "7-both-renamed-side-2.txt", + "7-both-renamed-side-2.txt", + "7-both-renamed.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 41); + create_index_names(checkout_name_entries, 9); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("0a-no-change.txt", + 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); + + ensure_workdir("0b-duplicated-in-ours.txt", + 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); + + ensure_workdir("0b-rewritten-in-ours.txt", + 0100644, "4c7e515d6d52d820496858f2f059ece69e99e2e3"); + + ensure_workdir("0c-duplicated-in-theirs.txt", + 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); + + ensure_workdir("0c-rewritten-in-theirs.txt", + 0100644, "4648d658682d1155c2a3db5b0c53305e26884ea5"); + + ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", + 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); + + ensure_workdir("1a-newname-in-ours.txt", + 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); + + ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", + 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); + + ensure_workdir("1b-newname-in-theirs.txt", + 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); + + ensure_workdir("2-newname-in-both.txt", + 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); + + ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", + 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); + + ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", + 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~ours", + 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~theirs", + 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~ours", + 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~theirs", + 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~ours", + 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~theirs", + 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~ours", + 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~theirs", + 0100644, "63247125386de9ec90a27ad36169307bf8a11a38"); + + ensure_workdir("6-both-renamed-1-to-2-ours.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("6-both-renamed-1-to-2-theirs.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("7-both-renamed.txt~ours", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + + ensure_workdir("7-both-renamed.txt~theirs", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +} + +void test_checkout_conflict__rename_keep_ours(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "3a-renamed-in-ours-deleted-in-theirs.txt", + "3a-newname-in-ours-deleted-in-theirs.txt", + "" + }, + + { + "3b-renamed-in-theirs-deleted-in-ours.txt", + "", + "3b-newname-in-theirs-deleted-in-ours.txt" + }, + + { + "4a-renamed-in-ours-added-in-theirs.txt", + "4a-newname-in-ours-added-in-theirs.txt", + "" + }, + + { + "4b-renamed-in-theirs-added-in-ours.txt", + "", + "4b-newname-in-theirs-added-in-ours.txt" + }, + + { + "5a-renamed-in-ours-added-in-theirs.txt", + "5a-newname-in-ours-added-in-theirs.txt", + "5a-renamed-in-ours-added-in-theirs.txt" + }, + + { + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-newname-in-theirs-added-in-ours.txt" + }, + + { + "6-both-renamed-1-to-2.txt", + "6-both-renamed-1-to-2-ours.txt", + "6-both-renamed-1-to-2-theirs.txt" + }, + + { + "7-both-renamed-side-1.txt", + "7-both-renamed.txt", + "7-both-renamed-side-1.txt" + }, + + { + "7-both-renamed-side-2.txt", + "7-both-renamed-side-2.txt", + "7-both-renamed.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + create_index(checkout_index_entries, 41); + create_index_names(checkout_name_entries, 9); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("0a-no-change.txt", + 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); + + ensure_workdir("0b-duplicated-in-ours.txt", + 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); + + ensure_workdir("0b-rewritten-in-ours.txt", + 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e"); + + ensure_workdir("0c-duplicated-in-theirs.txt", + 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); + + ensure_workdir("0c-rewritten-in-theirs.txt", + 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09"); + + ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", + 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); + + ensure_workdir("1a-newname-in-ours.txt", + 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); + + ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", + 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); + + ensure_workdir("1b-newname-in-theirs.txt", + 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); + + ensure_workdir("2-newname-in-both.txt", + 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); + + ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", + 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); + + ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", + 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt", + 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt", + 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt", + 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt", + 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); + + ensure_workdir("6-both-renamed-1-to-2-ours.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("7-both-renamed.txt", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); +} + +void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-one-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-one-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-one-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-one-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-one.txt" }, + + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-two-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-two-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-two-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-two-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-two.txt" }, + + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-three-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-three-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-three-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-three-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-three.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-three.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, + { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, + { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "test-one-side-one.txt", + "test-one.txt", + "test-one-side-one.txt" + }, + { + "test-one-side-two.txt", + "test-one-side-two.txt", + "test-one.txt" + }, + + { + "test-two-side-one.txt", + "test-two.txt", + "test-two-side-one.txt" + }, + { + "test-two-side-two.txt", + "test-two-side-two.txt", + "test-two.txt" + }, + + { + "test-three-side-one.txt", + "test-three.txt", + "test-three-side-one.txt" + }, + { + "test-three-side-two.txt", + "test-three-side-two.txt", + "test-three.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 24); + create_index_names(checkout_name_entries, 6); + git_index_write(g_index); + + /* Add some files on disk that conflict with the names that would be chosen + * for the files written for each side. */ + + cl_git_rewritefile("merge-resolve/test-one.txt~ours", + "Expect index contents to be written to ~ours_0"); + cl_git_rewritefile("merge-resolve/test-one.txt~theirs", + "Expect index contents to be written to ~theirs_0"); + + cl_git_rewritefile("merge-resolve/test-two.txt~ours", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_0", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_0", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_1", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_1", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_2", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_2", + "Expect index contents to be written to ~theirs_3"); + + cl_git_rewritefile("merge-resolve/test-three.txt~Ours", + "Expect case insensitive filesystems to create ~ours_0"); + cl_git_rewritefile("merge-resolve/test-three.txt~THEIRS", + "Expect case insensitive filesystems to create ~theirs_0"); + + cl_git_rewritefile("merge-resolve/directory_file-one~ours", + "Index contents written to ~ours_0 in this D/F conflict"); + cl_git_rewritefile("merge-resolve/directory_file-two~theirs", + "Index contents written to ~theirs_0 in this D/F conflict"); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("test-one.txt~ours_0", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-one.txt~theirs_0", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + + ensure_workdir("test-two.txt~ours_3", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-two.txt~theirs_3", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + + /* Name is mangled on case insensitive only */ +#if defined(GIT_WIN32) || defined(__APPLE__) + ensure_workdir("test-three.txt~ours_0", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs_0", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +#else + ensure_workdir("test-three.txt~ours", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +#endif + + ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID); + ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__update_only(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, + { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, + { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY; + + create_index(checkout_index_entries, 3); + git_index_write(g_index); + + cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777)); + cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); + ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID); + + cl_assert(!git_path_exists("merge-resolve/modify-delete")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one-side-one.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one-side-two.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt~ours")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt~theirs")); + cl_assert(!git_path_exists("merge-resolve/directory_file-one/file")); + cl_assert(!git_path_exists("merge-resolve/directory_file-one~ours")); + cl_assert(!git_path_exists("merge-resolve/directory_file-two~theirs")); +} + +void test_checkout_conflict__path_filters(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + char *paths[] = { "conflicting-1.txt", "conflicting-3.txt" }; + git_strarray patharray = {0}; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, + }; + + patharray.count = 2; + patharray.strings = paths; + + opts.paths = patharray; + + create_index(checkout_index_entries, 12); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting-1.txt", CONFLICTING_DIFF3_FILE); + cl_assert(!git_path_exists("merge-resolve/conflicting-2.txt")); + ensure_workdir_contents("conflicting-3.txt", AUTOMERGEABLE_MERGED_FILE); + cl_assert(!git_path_exists("merge-resolve/conflicting-4.txt")); +} + +static void collect_progress( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + git_vector *paths = payload; + + (void)completed_steps; + (void)total_steps; + + if (path == NULL) + return; + + git_vector_insert(paths, strdup(path)); +} + +void test_checkout_conflict__report_progress(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_vector paths = GIT_VECTOR_INIT; + char *path; + size_t i; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, + }; + + opts.progress_cb = collect_progress; + opts.progress_payload = &paths; + + + create_index(checkout_index_entries, 12); + git_index_write(g_index); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + cl_assert_equal_i(4, git_vector_length(&paths)); + cl_assert_equal_s("conflicting-1.txt", git_vector_get(&paths, 0)); + cl_assert_equal_s("conflicting-2.txt", git_vector_get(&paths, 1)); + cl_assert_equal_s("conflicting-3.txt", git_vector_get(&paths, 2)); + cl_assert_equal_s("conflicting-4.txt", git_vector_get(&paths, 3)); + + git_vector_foreach(&paths, i, path) + git__free(path); + + git_vector_free(&paths); +} diff --git a/tests-clar/checkout/crlf.c b/tests/checkout/crlf.c similarity index 56% rename from tests-clar/checkout/crlf.c rename to tests/checkout/crlf.c index 285b1f272..9a4cbd313 100644 --- a/tests-clar/checkout/crlf.c +++ b/tests/checkout/crlf.c @@ -1,18 +1,10 @@ #include "clar_libgit2.h" #include "checkout_helpers.h" +#include "../filter/crlf.h" #include "git2/checkout.h" #include "repository.h" - -#define UTF8_BOM "\xEF\xBB\xBF" -#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" -#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" -#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" -#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" - -#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" -#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" -#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" +#include "posix.h" static git_repository *g_repo; @@ -145,3 +137,95 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) git_index_free(index); } + +void test_checkout_crlf__with_ident(void) +{ + git_index *index; + git_blob *blob; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol=lf\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + /* add files with $Id$ */ + + cl_git_mkfile("crlf/lf.ident", ALL_LF_TEXT_RAW "\n$Id: initial content$\n"); + cl_git_mkfile("crlf/crlf.ident", ALL_CRLF_TEXT_RAW "\r\n$Id$\r\n\r\n"); + cl_git_mkfile("crlf/more1.identlf", "$Id$\n" MORE_LF_TEXT_RAW); + cl_git_mkfile("crlf/more2.identcrlf", "\r\n$Id: $\r\n" MORE_CRLF_TEXT_RAW); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "lf.ident")); + cl_git_pass(git_index_add_bypath(index, "crlf.ident")); + cl_git_pass(git_index_add_bypath(index, "more1.identlf")); + cl_git_pass(git_index_add_bypath(index, "more2.identcrlf")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Some ident files\n"); + + git_checkout_head(g_repo, &opts); + + /* check that blobs have $Id$ */ + + cl_git_pass(git_blob_lookup(&blob, g_repo, + & git_index_get_bypath(index, "lf.ident", 0)->oid)); + 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)->oid)); + cl_assert_equal_s( + "\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob)); + git_blob_free(blob); + + /* check that filesystem is initially untouched - matching core Git */ + + cl_assert_equal_file( + ALL_LF_TEXT_RAW "\n$Id: initial content$\n", 0, "crlf/lf.ident"); + + /* check that forced checkout rewrites correctly */ + + p_unlink("crlf/lf.ident"); + p_unlink("crlf/crlf.ident"); + p_unlink("crlf/more1.identlf"); + p_unlink("crlf/more2.identcrlf"); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) { + cl_assert_equal_file( + ALL_LF_TEXT_RAW + "\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\n", + 0, "crlf/lf.ident"); + cl_assert_equal_file( + ALL_CRLF_TEXT_AS_LF + "\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\n\n", + 0, "crlf/crlf.ident"); + } else { + cl_assert_equal_file( + ALL_LF_TEXT_AS_CRLF + "\r\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\r\n", + 0, "crlf/lf.ident"); + cl_assert_equal_file( + ALL_CRLF_TEXT_RAW + "\r\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\r\n\r\n", + 0, "crlf/crlf.ident"); + } + + cl_assert_equal_file( + "$Id: f7830382dac1f1583422be5530fdfbd26289431b$\n" + MORE_LF_TEXT_AS_LF, 0, "crlf/more1.identlf"); + + cl_assert_equal_file( + "\r\n$Id: 74677a68413012ce8d7e7cfc3f12603df3a3eac4$\r\n" + MORE_CRLF_TEXT_AS_CRLF, 0, "crlf/more2.identcrlf"); + + git_index_free(index); +} diff --git a/tests-clar/checkout/head.c b/tests/checkout/head.c similarity index 86% rename from tests-clar/checkout/head.c rename to tests/checkout/head.c index 46646f8bf..a7a7e9071 100644 --- a/tests-clar/checkout/head.c +++ b/tests/checkout/head.c @@ -16,11 +16,11 @@ void test_checkout_head__cleanup(void) cl_git_sandbox_cleanup(); } -void test_checkout_head__orphaned_head_returns_GIT_EORPHANEDHEAD(void) +void test_checkout_head__unborn_head_returns_GIT_EUNBORNBRANCH(void) { - make_head_orphaned(g_repo, NON_EXISTING_HEAD); + make_head_unborn(g_repo, NON_EXISTING_HEAD); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL)); + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_checkout_head(g_repo, NULL)); } void test_checkout_head__with_index_only_tree(void) @@ -54,7 +54,6 @@ void test_checkout_head__with_index_only_tree(void) cl_git_pass(git_checkout_head(g_repo, &opts)); cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read(index)); /* reload if needed */ cl_assert(!git_path_isfile("testrepo/newdir/newfile.txt")); cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL); diff --git a/tests-clar/checkout/index.c b/tests/checkout/index.c similarity index 97% rename from tests-clar/checkout/index.c rename to tests/checkout/index.c index 9d8b321ae..48d6d79f9 100644 --- a/tests-clar/checkout/index.c +++ b/tests/checkout/index.c @@ -224,11 +224,14 @@ void test_checkout_index__options_disable_filters(void) void test_checkout_index__options_dir_modes(void) { -#ifndef GIT_WIN32 git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; struct stat st; git_oid oid; git_commit *commit; + mode_t um; + + if (!cl_is_chmod_supported()) + return; cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); @@ -240,31 +243,34 @@ void test_checkout_index__options_dir_modes(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + /* umask will influence actual directory creation mode */ + (void)p_umask(um = p_umask(022)); + cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); + cl_assert_equal_i_fmt(st.st_mode, (GIT_FILEMODE_TREE | 0701) & ~um, "%07o"); /* File-mode test, since we're on the 'dir' branch */ cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); + cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_BLOB_EXECUTABLE, "%07o"); git_commit_free(commit); -#endif } void test_checkout_index__options_override_file_modes(void) { -#ifndef GIT_WIN32 git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; struct stat st; + if (!cl_is_chmod_supported()) + return; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; opts.file_mode = 0700; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); -#endif + cl_assert_equal_i_fmt(st.st_mode & GIT_MODE_PERMS_MASK, 0700, "%07o"); } void test_checkout_index__options_open_flags(void) @@ -587,6 +593,8 @@ void test_checkout_index__target_directory_from_bare(void) cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_object_free(head); } void test_checkout_index__can_get_repo_from_index(void) diff --git a/tests-clar/checkout/tree.c b/tests/checkout/tree.c similarity index 87% rename from tests-clar/checkout/tree.c rename to tests/checkout/tree.c index e4bfbce06..66b01bc7f 100644 --- a/tests-clar/checkout/tree.c +++ b/tests/checkout/tree.c @@ -677,3 +677,66 @@ void test_checkout_tree__target_directory_from_bare(void) cl_git_pass(git_futils_rmdir_r( "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); } + +void test_checkout_tree__extremely_long_file_name(void) +{ + // A utf-8 string with 83 characters, but 249 bytes. + const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + char path[1024]; + + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + sprintf(path, "testrepo/%s.txt", longname); + cl_assert(git_path_exists(path)); + + git_object_free(g_object); + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_assert(!git_path_exists(path)); +} + +static void create_conflict(void) +{ + git_index *index; + git_index_entry entry; + + cl_git_pass(git_repository_index(&index, g_repo)); + + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.mode = 0100644; + entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT; + git_oid_fromstr(&entry.oid, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + entry.path = "conflicts.txt"; + cl_git_pass(git_index_add(index, &entry)); + + entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT; + git_oid_fromstr(&entry.oid, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + cl_git_pass(git_index_add(index, &entry)); + + entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT; + git_oid_fromstr(&entry.oid, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + cl_git_pass(git_index_add(index, &entry)); + + git_index_write(index); + git_index_free(index); +} + +void test_checkout_tree__fails_when_conflicts_exist_in_index(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + create_conflict(); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + + git_object_free(obj); +} diff --git a/tests-clar/checkout/typechange.c b/tests/checkout/typechange.c similarity index 100% rename from tests-clar/checkout/typechange.c rename to tests/checkout/typechange.c diff --git a/tests-clar/clar.c b/tests/clar.c similarity index 77% rename from tests-clar/clar.c rename to tests/clar.c index fb10dd397..6c7399a54 100644 --- a/tests-clar/clar.c +++ b/tests/clar.c @@ -24,28 +24,59 @@ # define _MAIN_CC __cdecl -# define stat(path, st) _stat(path, st) -# define mkdir(path, mode) _mkdir(path) -# define chdir(path) _chdir(path) -# define access(path, mode) _access(path, mode) -# define strdup(str) _strdup(str) -# define strcasecmp(a,b) _stricmp(a,b) +# ifndef stat +# define stat(path, st) _stat(path, st) +# endif +# ifndef mkdir +# define mkdir(path, mode) _mkdir(path) +# endif +# ifndef chdir +# define chdir(path) _chdir(path) +# endif +# ifndef access +# define access(path, mode) _access(path, mode) +# endif +# ifndef strdup +# define strdup(str) _strdup(str) +# endif +# ifndef strcasecmp +# define strcasecmp(a,b) _stricmp(a,b) +# endif # ifndef __MINGW32__ # pragma comment(lib, "shell32") -# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) -# define W_OK 02 -# define S_ISDIR(x) ((x & _S_IFDIR) != 0) -# define snprint_eq(buf,sz,fmt,a,b) _snprintf_s(buf,sz,_TRUNCATE,fmt,a,b) +# ifndef strncpy +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) +# endif +# ifndef W_OK +# define W_OK 02 +# endif +# ifndef S_ISDIR +# define S_ISDIR(x) ((x & _S_IFDIR) != 0) +# endif +# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) # else -# define snprint_eq snprintf +# define p_snprintf snprintf +# endif + +# ifndef PRIuZ +# define PRIuZ "Iu" +# endif +# ifndef PRIxZ +# define PRIxZ "Ix" # endif typedef struct _stat STAT_T; #else # include /* waitpid(2) */ # include # define _MAIN_CC -# define snprint_eq snprintf +# define p_snprintf snprintf +# ifndef PRIuZ +# define PRIuZ "zu" +# endif +# ifndef PRIxZ +# define PRIxZ "zx" +# endif typedef struct stat STAT_T; #endif @@ -406,45 +437,66 @@ void clar__assert( clar__fail(file, line, error_msg, description, should_abort); } -void clar__assert_equal_s( - const char *s1, - const char *s2, +void clar__assert_equal( const char *file, int line, const char *err, - int should_abort) + int should_abort, + const char *fmt, + ...) { - int match = (s1 == NULL || s2 == NULL) ? (s1 == s2) : (strcmp(s1, s2) == 0); + va_list args; + char buf[4096]; + int is_equal = 1; - if (!match) { - char buf[4096]; + va_start(args, fmt); - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) - /* find differing byte offset */; - snprint_eq(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", s1, s2, pos); - } else { - snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + if (!strcmp("%s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", + s1, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } } - - clar__fail(file, line, err, buf, should_abort); } -} - -void clar__assert_equal_i( - int i1, - int i2, - const char *file, - int line, - const char *err, - int should_abort) -{ - if (i1 != i2) { - char buf[128]; - snprint_eq(buf, sizeof(buf), "%d != %d", i1, i2); - clar__fail(file, line, err, buf, should_abort); + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); + is_equal = (sz1 == sz2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); + } } + else if (!strcmp("%p", fmt)) { + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); + is_equal = (p1 == p2); + if (!is_equal) + p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); + } + else { + int i1 = va_arg(args, int), i2 = va_arg(args, int); + is_equal = (i1 == i2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, i1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); + } + } + + va_end(args); + + if (!is_equal) + clar__fail(file, line, err, buf, should_abort); } void cl_set_cleanup(void (*cleanup)(void *), void *opaque) diff --git a/tests-clar/clar.h b/tests/clar.h similarity index 69% rename from tests-clar/clar.h rename to tests/clar.h index d92318bd4..e1f244eba 100644 --- a/tests-clar/clar.h +++ b/tests/clar.h @@ -57,15 +57,17 @@ void cl_fixture_cleanup(const char *fixture_name); /** * Typed assertion macros */ -#define cl_assert_equal_s(s1,s2) clar__assert_equal_s((s1),(s2),__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1) -#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal_s((s1),(s2),__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1) +#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) -#define cl_assert_equal_i(i1,i2) clar__assert_equal_i((i1),(i2),__FILE__,__LINE__,#i1 " != " #i2, 1) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal_i((i1),(i2),__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1) +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) -#define cl_assert_equal_b(b1,b2) clar__assert_equal_i(!!(b1),!!(b2),__FILE__,__LINE__,#b1 " != " #b2, 1) +#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) + +#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) -#define cl_assert_equal_p(p1,p2) cl_assert((p1) == (p2)) void clar__fail( const char *file, @@ -82,7 +84,12 @@ void clar__assert( const char *description, int should_abort); -void clar__assert_equal_s(const char *,const char *,const char *,int,const char *,int); -void clar__assert_equal_i(int,int,const char *,int,const char *,int); +void clar__assert_equal( + const char *file, + int line, + const char *err, + int should_abort, + const char *fmt, + ...); #endif diff --git a/tests-clar/clar/fixtures.h b/tests/clar/fixtures.h similarity index 100% rename from tests-clar/clar/fixtures.h rename to tests/clar/fixtures.h diff --git a/tests-clar/clar/fs.h b/tests/clar/fs.h similarity index 100% rename from tests-clar/clar/fs.h rename to tests/clar/fs.h diff --git a/tests-clar/clar/print.h b/tests/clar/print.h similarity index 100% rename from tests-clar/clar/print.h rename to tests/clar/print.h diff --git a/tests-clar/clar/sandbox.h b/tests/clar/sandbox.h similarity index 94% rename from tests-clar/clar/sandbox.h rename to tests/clar/sandbox.h index 1ca6fcae8..ee7564148 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests/clar/sandbox.h @@ -43,10 +43,8 @@ find_tmp_path(char *buffer, size_t length) } #else - DWORD env_len; - - if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && - env_len < length) + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); + if (env_len > 0 && env_len < (DWORD)length) return 0; if (GetTempPath((DWORD)length, buffer)) diff --git a/tests-clar/clar_libgit2.c b/tests/clar_libgit2.c similarity index 61% rename from tests-clar/clar_libgit2.c rename to tests/clar_libgit2.c index de0e41bf7..50762cdb8 100644 --- a/tests-clar/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "posix.h" #include "path.h" +#include "git2/sys/repository.h" void cl_git_report_failure( int error, const char *file, int line, const char *fncall) @@ -30,24 +31,26 @@ void cl_git_mkfile(const char *filename, const char *content) } void cl_git_write2file( - const char *filename, const char *new_content, int flags, unsigned int mode) + const char *path, const char *content, size_t content_len, + int flags, unsigned int mode) { - int fd = p_open(filename, flags, mode); - cl_assert(fd >= 0); - if (!new_content) - new_content = "\n"; - cl_must_pass(p_write(fd, new_content, strlen(new_content))); + int fd; + cl_assert(path && content); + cl_assert((fd = p_open(path, flags, mode)) >= 0); + if (!content_len) + content_len = strlen(content); + cl_must_pass(p_write(fd, content, content_len)); cl_must_pass(p_close(fd)); } -void cl_git_append2file(const char *filename, const char *new_content) +void cl_git_append2file(const char *path, const char *content) { - cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_APPEND, 0644); + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644); } -void cl_git_rewritefile(const char *filename, const char *new_content) +void cl_git_rewritefile(const char *path, const char *content) { - cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_TRUNC, 0644); + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); } #ifdef GIT_WIN32 @@ -56,23 +59,24 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - wchar_t name_utf16[GIT_WIN_PATH]; + git_win32_path name_utf16; DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; - git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); + git_win32_path_from_c(name_utf16, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; - alloc_len = GIT_WIN_PATH; cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); - cl_assert(value_utf8 = git__malloc(alloc_len)); - git__utf16_to_8(value_utf8, value_utf16); + alloc_len = alloc_len * 4 + 1; /* worst case UTF16->UTF8 growth */ + cl_assert(value_utf8 = git__calloc(alloc_len, 1)); + + git__utf16_to_8(value_utf8, alloc_len, value_utf16); git__free(value_utf16); @@ -81,13 +85,13 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - wchar_t name_utf16[GIT_WIN_PATH]; - wchar_t value_utf16[GIT_WIN_PATH]; + git_win32_path name_utf16; + git_win32_path value_utf16; - git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); + git_win32_path_from_c(name_utf16, name); if (value) { - git__utf8_to_16(value_utf16, GIT_WIN_PATH, value); + git_win32_path_from_c(value_utf16, value); cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when @@ -107,12 +111,12 @@ int cl_setenv(const char *name, const char *value) * the source is a directory, a child of the source). */ int cl_rename(const char *source, const char *dest) { - wchar_t source_utf16[GIT_WIN_PATH]; - wchar_t dest_utf16[GIT_WIN_PATH]; + git_win32_path source_utf16; + git_win32_path dest_utf16; unsigned retries = 1; - git__utf8_to_16(source_utf16, GIT_WIN_PATH, source); - git__utf8_to_16(dest_utf16, GIT_WIN_PATH, dest); + git_win32_path_from_c(source_utf16, source); + git_win32_path_from_c(dest_utf16, dest); while (!MoveFileW(source_utf16, dest_utf16)) { /* Only retry if the error is ERROR_ACCESS_DENIED; @@ -187,6 +191,9 @@ git_repository *cl_git_sandbox_init(const char *sandbox) /* Now open the sandbox repository and make it available for tests */ cl_git_pass(git_repository_open(&_cl_repo, sandbox)); + /* Adjust configs after copying to new filesystem */ + cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0)); + return _cl_repo; } @@ -298,7 +305,7 @@ static int remove_placeholders_recurs(void *_data, git_buf *path) size_t pathlen; if (git_path_isdir(path->ptr) == true) - return git_path_direach(path, remove_placeholders_recurs, data); + return git_path_direach(path, 0, remove_placeholders_recurs, data); pathlen = path->size; @@ -336,6 +343,65 @@ int cl_git_remove_placeholders(const char *directory_path, const char *filename) return error; } +#define CL_COMMIT_NAME "Libgit2 Tester" +#define CL_COMMIT_EMAIL "libgit2-test@github.com" +#define CL_COMMIT_MSG "Test commit of tree " + +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg) +{ + git_index *index; + git_oid commit_id, tree_id; + git_object *parent = NULL; + git_reference *ref = NULL; + git_tree *tree = NULL; + char buf[128]; + int free_sig = (sig == NULL); + + /* it is fine if looking up HEAD fails - we make this the first commit */ + git_revparse_ext(&parent, &ref, repo, "HEAD"); + + /* write the index content as a tree */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + + if (sig) + cl_assert(sig->name && sig->email); + else if (!time) + cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL)); + else + cl_git_pass(git_signature_new( + &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0)); + + if (!msg) { + strcpy(buf, CL_COMMIT_MSG); + git_oid_tostr(buf + strlen(CL_COMMIT_MSG), + sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id); + msg = buf; + } + + cl_git_pass(git_commit_create_v( + &commit_id, repo, ref ? git_reference_name(ref) : "HEAD", + sig, sig, NULL, msg, tree, parent ? 1 : 0, parent)); + + if (out) + git_oid_cpy(out, &commit_id); + + git_object_free(parent); + git_reference_free(ref); + if (free_sig) + git_signature_free(sig); + git_tree_free(tree); +} + void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) { git_config *config; @@ -343,3 +409,75 @@ void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) cl_git_pass(git_config_set_bool(config, cfg, value != 0)); git_config_free(config); } + +int cl_repo_get_bool(git_repository *repo, const char *cfg) +{ + int val = 0; + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_get_bool(&val, config, cfg));; + git_config_free(config); + return val; +} + +/* this is essentially the code from git__unescape modified slightly */ +static size_t strip_cr_from_buf(char *start, size_t len) +{ + char *scan, *trail, *end = start + len; + + for (scan = trail = start; scan < end; trail++, scan++) { + while (*scan == '\r') + scan++; /* skip '\r' */ + + if (trail != scan) + *trail = *scan; + } + + *trail = '\0'; + + return (trail - start); +} + +void clar__assert_equal_file( + const char *expected_data, + size_t expected_bytes, + int ignore_cr, + const char *path, + const char *file, + int line) +{ + char buf[4000]; + ssize_t bytes, total_bytes = 0; + int fd = p_open(path, O_RDONLY | O_BINARY); + cl_assert(fd >= 0); + + if (expected_data && !expected_bytes) + expected_bytes = strlen(expected_data); + + while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) { + clar__assert( + bytes > 0, file, line, "error reading from file", path, 1); + + if (ignore_cr) + bytes = strip_cr_from_buf(buf, bytes); + + if (memcmp(expected_data, buf, bytes) != 0) { + int pos; + for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos) + /* find differing byte offset */; + p_snprintf( + buf, sizeof(buf), "file content mismatch at byte %d", + (int)(total_bytes + pos)); + clar__fail(file, line, buf, path, 1); + } + + expected_data += bytes; + total_bytes += bytes; + } + + p_close(fd); + + clar__assert(!bytes, file, line, "error reading from file", path, 1); + clar__assert_equal(file, line, "mismatched file length", 1, "%"PRIuZ, + (size_t)expected_bytes, (size_t)total_bytes); +} diff --git a/tests-clar/clar_libgit2.h b/tests/clar_libgit2.h similarity index 59% rename from tests-clar/clar_libgit2.h rename to tests/clar_libgit2.h index 3fcf45a37..b9ef5627e 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -29,7 +29,41 @@ void cl_git_report_failure(int, const char *, int, const char *); -#define cl_assert_equal_sz(sz1,sz2) cl_assert_equal_i((int)sz1, (int)(sz2)) +#define cl_assert_at_line(expr,file,line) \ + clar__assert((expr) != 0, file, line, "Expression is not true: " #expr, NULL, 1) + +GIT_INLINE(void) clar__assert_in_range( + int lo, int val, int hi, + const char *file, int line, const char *err, int should_abort) +{ + if (lo > val || hi < val) { + char buf[128]; + snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); + clar__fail(file, line, err, buf, should_abort); + } +} + +#define cl_assert_equal_sz(sz1,sz2) do { \ + size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \ + clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \ +} while (0) + +#define cl_assert_in_range(L,V,H) \ + clar__assert_in_range((L),(V),(H),__FILE__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) + +#define cl_assert_equal_file(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,(int)__LINE__) + +#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,(int)__LINE__) + +void clar__assert_equal_file( + const char *expected_data, + size_t expected_size, + int ignore_cr, + const char *path, + const char *file, + int line); /* * Some utility macros for building long strings @@ -44,7 +78,8 @@ void cl_git_report_failure(int, const char *, int, const char *); void cl_git_mkfile(const char *filename, const char *content); void cl_git_append2file(const char *filename, const char *new_content); void cl_git_rewritefile(const char *filename, const char *new_content); -void cl_git_write2file(const char *filename, const char *new_content, int flags, unsigned int mode); +void cl_git_write2file(const char *path, const char *data, + size_t datalen, int flags, unsigned int mode); bool cl_toggle_filemode(const char *filename); bool cl_is_chmod_supported(void); @@ -69,7 +104,16 @@ const char* cl_git_path_url(const char *path); /* Test repository cleaner */ int cl_git_remove_placeholders(const char *directory_path, const char *filename); +/* commit creation helpers */ +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg); + /* config setting helpers */ void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); +int cl_repo_get_bool(git_repository *repo, const char *cfg); #endif diff --git a/tests-clar/clone/empty.c b/tests/clone/empty.c similarity index 92% rename from tests-clar/clone/empty.c rename to tests/clone/empty.c index f92fa6cbb..6d19244cc 100644 --- a/tests-clar/clone/empty.c +++ b/tests/clone/empty.c @@ -10,12 +10,14 @@ static git_repository *g_repo_cloned; void test_clone_empty__initialize(void) { git_repository *sandbox = cl_git_sandbox_init("empty_bare.git"); + git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT; cl_git_remove_placeholders(git_repository_path(sandbox), "dummy-marker.txt"); g_repo = NULL; memset(&g_options, 0, sizeof(git_clone_options)); g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.remote_callbacks = dummy_callbacks; } void test_clone_empty__cleanup(void) @@ -44,7 +46,7 @@ void test_clone_empty__can_clone_an_empty_local_repo_barely(void) g_options.bare = true; cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); - /* Although the HEAD is orphaned... */ + /* Although the HEAD is unborn... */ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name)); /* ...one can still retrieve the name of the remote tracking reference */ @@ -59,7 +61,7 @@ void test_clone_empty__can_clone_an_empty_local_repo_barely(void) cl_assert_equal_s(expected_remote_name, buffer); - /* ...even when the remote HEAD is orphaned as well */ + /* ...even when the remote HEAD is unborn as well */ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, expected_tracked_branch_name)); } diff --git a/tests-clar/clone/nonetwork.c b/tests/clone/nonetwork.c similarity index 58% rename from tests-clar/clone/nonetwork.c rename to tests/clone/nonetwork.c index 339b1e70d..a286e2a8f 100644 --- a/tests-clar/clone/nonetwork.c +++ b/tests/clone/nonetwork.c @@ -15,6 +15,7 @@ static git_remote* g_remote; void test_clone_nonetwork__initialize(void) { git_checkout_opts dummy_opts = GIT_CHECKOUT_OPTS_INIT; + git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT; g_repo = NULL; @@ -22,6 +23,7 @@ void test_clone_nonetwork__initialize(void) g_options.version = GIT_CLONE_OPTIONS_VERSION; g_options.checkout_opts = dummy_opts; g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_options.remote_callbacks = dummy_callbacks; } void test_clone_nonetwork__cleanup(void) @@ -44,7 +46,7 @@ void test_clone_nonetwork__cleanup(void) cl_fixture_cleanup("./foo"); } -void test_clone_nonetwork__bad_url(void) +void test_clone_nonetwork__bad_urls(void) { /* Clone should clean up the mess if the URL isn't a git repository */ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); @@ -52,43 +54,27 @@ void test_clone_nonetwork__bad_url(void) g_options.bare = true; cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); cl_assert(!git_path_exists("./foo")); -} -static int dont_call_me(void *state, git_buf *path) -{ - GIT_UNUSED(state); - GIT_UNUSED(path); - return GIT_ERROR; + cl_git_fail(git_clone(&g_repo, "git://example.com:asdf", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "https://example.com:asdf/foo", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "git://github.com/git://github.com/foo/bar.git.git", + "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "arrbee:my/bad:password@github.com:1111/strange:words.git", + "./foo", &g_options)); } void test_clone_nonetwork__do_not_clean_existing_directory(void) { - git_buf path_buf = GIT_BUF_INIT; - - git_buf_put(&path_buf, "./foo", 5); - /* Clone should not remove the directory if it already exists, but * Should clean up entries it creates. */ p_mkdir("./foo", GIT_DIR_MODE); cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(git_path_exists("./foo")); - - /* Make sure the directory is empty. */ - cl_git_pass(git_path_direach(&path_buf, - dont_call_me, - NULL)); + cl_assert(git_path_is_empty_dir("./foo")); /* Try again with a bare repository. */ g_options.bare = true; cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(git_path_exists("./foo")); - - /* Make sure the directory is empty. */ - cl_git_pass(git_path_direach(&path_buf, - dont_call_me, - NULL)); - - git_buf_free(&path_buf); + cl_assert(git_path_is_empty_dir("./foo")); } void test_clone_nonetwork__local(void) @@ -124,84 +110,17 @@ void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(vo void test_clone_nonetwork__custom_origin_name(void) { - g_options.remote_name = "my_origin"; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + g_options.remote_name = "my_origin"; + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - cl_git_pass(git_remote_load(&g_remote, g_repo, "my_origin")); + cl_git_pass(git_remote_load(&g_remote, g_repo, "my_origin")); } -void test_clone_nonetwork__custom_push_url(void) +void test_clone_nonetwork__defaults(void) { - const char *url = "http://example.com"; - - g_options.pushurl = url; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", NULL)); + cl_assert(g_repo); cl_git_pass(git_remote_load(&g_remote, g_repo, "origin")); - cl_assert_equal_s(url, git_remote_pushurl(g_remote)); -} - -void test_clone_nonetwork__custom_fetch_spec(void) -{ - const git_refspec *actual_fs; - const char *spec = "+refs/heads/master:refs/heads/foo"; - - g_options.fetch_spec = spec; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_remote_load(&g_remote, g_repo, "origin")); - actual_fs = git_remote_get_refspec(g_remote, 0); - cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs)); - cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs)); - - cl_git_pass(git_reference_lookup(&g_ref, g_repo, "refs/heads/foo")); -} - -void test_clone_nonetwork__custom_push_spec(void) -{ - const git_refspec *actual_fs; - const char *spec = "+refs/heads/master:refs/heads/foo"; - - g_options.push_spec = spec; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_remote_load(&g_remote, g_repo, "origin")); - actual_fs = git_remote_get_refspec(g_remote, git_remote_refspec_count(g_remote) - 1); - cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs)); - cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs)); -} - -void test_clone_nonetwork__custom_autotag(void) -{ - git_remote *origin; - git_strarray tags = {0}; - - g_options.remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_NONE; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_tag_list(&tags, g_repo)); - cl_assert_equal_sz(0, tags.count); - - cl_git_pass(git_remote_load(&origin, g_repo, "origin")); - cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_NONE, origin->download_tags); - - git_strarray_free(&tags); - git_remote_free(origin); -} - -void test_clone_nonetwork__custom_autotag_tags_all(void) -{ - git_strarray tags = {0}; - git_remote *origin; - - g_options.remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_remote_load(&origin, g_repo, "origin")); - cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_ALL, origin->download_tags); - - git_strarray_free(&tags); - git_remote_free(origin); } void test_clone_nonetwork__cope_with_already_existing_directory(void) @@ -228,7 +147,7 @@ void test_clone_nonetwork__can_checkout_given_branch(void) g_options.checkout_branch = "test"; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - cl_assert_equal_i(0, git_repository_head_orphan(g_repo)); + cl_assert_equal_i(0, git_repository_head_unborn(g_repo)); cl_git_pass(git_repository_head(&g_ref, g_repo)); cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test"); diff --git a/tests-clar/commit/commit.c b/tests/commit/commit.c similarity index 100% rename from tests-clar/commit/commit.c rename to tests/commit/commit.c diff --git a/tests-clar/commit/parent.c b/tests/commit/parent.c similarity index 100% rename from tests-clar/commit/parent.c rename to tests/commit/parent.c diff --git a/tests-clar/commit/parse.c b/tests/commit/parse.c similarity index 74% rename from tests-clar/commit/parse.c rename to tests/commit/parse.c index 415860a6e..41e162440 100644 --- a/tests-clar/commit/parse.c +++ b/tests/commit/parse.c @@ -7,77 +7,77 @@ static git_repository *g_repo; void test_commit_parse__initialize(void) { - g_repo = cl_git_sandbox_init("testrepo"); + g_repo = cl_git_sandbox_init("testrepo"); } void test_commit_parse__cleanup(void) { - cl_git_sandbox_cleanup(); + cl_git_sandbox_cleanup(); } // Header parsing typedef struct { - const char *line; - const char *header; + const char *line; + const char *header; } parse_test_case; static parse_test_case passing_header_cases[] = { - { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, - { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading " }, - { "stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading" }, - { "tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree " }, - { "tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree " }, - { "tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree " }, - { "tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree " }, - { NULL, NULL } + { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, + { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading " }, + { "stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading" }, + { "tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree " }, + { "tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree " }, + { "tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree " }, + { "tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree " }, + { NULL, NULL } }; static parse_test_case failing_header_cases[] = { - { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent " }, - { "05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent " }, - { "parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent " }, - { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, - { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent " }, - { "", "tree " }, - { "", "" }, - { NULL, NULL } + { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent " }, + { "05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent " }, + { "parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent " }, + { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, + { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent " }, + { "", "tree " }, + { "", "" }, + { NULL, NULL } }; void test_commit_parse__header(void) { - git_oid oid; + git_oid oid; - parse_test_case *testcase; - for (testcase = passing_header_cases; testcase->line != NULL; testcase++) - { - const char *line = testcase->line; - const char *line_end = line + strlen(line); + parse_test_case *testcase; + for (testcase = passing_header_cases; testcase->line != NULL; testcase++) + { + const char *line = testcase->line; + const char *line_end = line + strlen(line); - cl_git_pass(git_oid__parse(&oid, &line, line_end, testcase->header)); - cl_assert(line == line_end); - } + cl_git_pass(git_oid__parse(&oid, &line, line_end, testcase->header)); + cl_assert(line == line_end); + } - for (testcase = failing_header_cases; testcase->line != NULL; testcase++) - { - const char *line = testcase->line; - const char *line_end = line + strlen(line); + for (testcase = failing_header_cases; testcase->line != NULL; testcase++) + { + const char *line = testcase->line; + const char *line_end = line + strlen(line); - cl_git_fail(git_oid__parse(&oid, &line, line_end, testcase->header)); - } + cl_git_fail(git_oid__parse(&oid, &line, line_end, testcase->header)); + } } // Signature parsing typedef struct { - const char *string; - const char *header; - const char *name; - const char *email; - git_time_t time; - int offset; + const char *string; + const char *header; + const char *name; + const char *email; + git_time_t time; + int offset; } passing_signature_test_case; passing_signature_test_case passing_signature_cases[] = { @@ -122,12 +122,12 @@ passing_signature_test_case passing_signature_cases[] = { {"author Vicent Marti 4294967296 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 4294967296, 0}, {"author Vicent Marti 8589934592 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 8589934592, 0}, - {NULL,NULL,NULL,NULL,0,0} + {NULL,NULL,NULL,NULL,0,0} }; typedef struct { - const char *string; - const char *header; + const char *string; + const char *header; } failing_signature_test_case; failing_signature_test_case failing_signature_cases[] = { @@ -143,31 +143,31 @@ failing_signature_test_case failing_signature_cases[] = { void test_commit_parse__signature(void) { - passing_signature_test_case *passcase; - failing_signature_test_case *failcase; + passing_signature_test_case *passcase; + failing_signature_test_case *failcase; - for (passcase = passing_signature_cases; passcase->string != NULL; passcase++) - { - const char *str = passcase->string; - size_t len = strlen(passcase->string); - struct git_signature person = {0}; + for (passcase = passing_signature_cases; passcase->string != NULL; passcase++) + { + const char *str = passcase->string; + size_t len = strlen(passcase->string); + struct git_signature person = {0}; - cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n')); - cl_assert_equal_s(passcase->name, person.name); - cl_assert_equal_s(passcase->email, person.email); - cl_assert_equal_i((int)passcase->time, (int)person.when.time); - cl_assert_equal_i(passcase->offset, person.when.offset); - git__free(person.name); git__free(person.email); - } + cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n')); + cl_assert_equal_s(passcase->name, person.name); + cl_assert_equal_s(passcase->email, person.email); + cl_assert_equal_i((int)passcase->time, (int)person.when.time); + cl_assert_equal_i(passcase->offset, person.when.offset); + git__free(person.name); git__free(person.email); + } - for (failcase = failing_signature_cases; failcase->string != NULL; failcase++) - { - const char *str = failcase->string; - size_t len = strlen(failcase->string); - git_signature person = {0}; - cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n')); - git__free(person.name); git__free(person.email); - } + for (failcase = failing_signature_cases; failcase->string != NULL; failcase++) + { + const char *str = failcase->string; + size_t len = strlen(failcase->string); + git_signature person = {0}; + cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n')); + git__free(person.name); git__free(person.email); + } } @@ -312,17 +312,17 @@ void test_commit_parse__entire_commit(void) // query the details on a parsed commit void test_commit_parse__details0(void) { - static const char *commit_ids[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", /* 6 */ - }; + static const char *commit_ids[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", /* 6 */ + }; const size_t commit_count = sizeof(commit_ids) / sizeof(const char *); - unsigned int i; + unsigned int i; for (i = 0; i < commit_count; ++i) { git_oid id; @@ -349,7 +349,6 @@ void test_commit_parse__details0(void) { cl_assert_equal_s("Scott Chacon", committer->name); cl_assert_equal_s("schacon@gmail.com", committer->email); cl_assert(message != NULL); - cl_assert(strchr(message, '\n') != NULL); cl_assert(commit_time > 0); cl_assert(parents <= 2); for (p = 0;p < parents;p++) { @@ -382,11 +381,33 @@ committer Vicent Marti 1273848544 +0200\n\ \n\ This commit has a few LF at the start of the commit message"; const char *message = +"This commit has a few LF at the start of the commit message"; + const char *raw_message = "\n\ \n\ This commit has a few LF at the start of the commit message"; + cl_git_pass(parse_commit(&commit, buffer)); + cl_assert_equal_s(message, git_commit_message(commit)); + cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); + git_commit__free(commit); +} + +void test_commit_parse__only_lf(void) +{ + git_commit *commit; + const char *buffer = +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent e90810b8df3e80c413d903f631643c716887138d\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +\n\ +\n"; + const char *message = ""; + const char *raw_message = "\n\n"; cl_git_pass(parse_commit(&commit, buffer)); cl_assert_equal_s(message, git_commit_message(commit)); + cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); git_commit__free(commit); } diff --git a/tests-clar/commit/signature.c b/tests/commit/signature.c similarity index 100% rename from tests-clar/commit/signature.c rename to tests/commit/signature.c diff --git a/tests-clar/commit/write.c b/tests/commit/write.c similarity index 100% rename from tests-clar/commit/write.c rename to tests/commit/write.c diff --git a/tests-clar/config/add.c b/tests/config/add.c similarity index 100% rename from tests-clar/config/add.c rename to tests/config/add.c diff --git a/tests-clar/config/backend.c b/tests/config/backend.c similarity index 100% rename from tests-clar/config/backend.c rename to tests/config/backend.c diff --git a/tests-clar/config/config_helpers.c b/tests/config/config_helpers.c similarity index 100% rename from tests-clar/config/config_helpers.c rename to tests/config/config_helpers.c diff --git a/tests-clar/config/config_helpers.h b/tests/config/config_helpers.h similarity index 100% rename from tests-clar/config/config_helpers.h rename to tests/config/config_helpers.h diff --git a/tests-clar/config/configlevel.c b/tests/config/configlevel.c similarity index 100% rename from tests-clar/config/configlevel.c rename to tests/config/configlevel.c diff --git a/tests-clar/config/global.c b/tests/config/global.c similarity index 68% rename from tests-clar/config/global.c rename to tests/config/global.c index 2ecdf97d8..d5f95f504 100644 --- a/tests-clar/config/global.c +++ b/tests/config/global.c @@ -6,19 +6,22 @@ void test_config_global__initialize(void) { git_buf path = GIT_BUF_INIT; - cl_must_pass(p_mkdir("home", 0777)); + cl_assert_equal_i(0, p_mkdir("home", 0777)); cl_git_pass(git_path_prettify(&path, "home", NULL)); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_must_pass(p_mkdir("xdg", 0777)); - cl_git_pass(git_path_prettify(&path, "xdg", NULL)); + cl_assert_equal_i(0, p_mkdir("xdg", 0777)); + cl_assert_equal_i(0, p_mkdir("xdg/git", 0777)); + cl_git_pass(git_path_prettify(&path, "xdg/git", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + cl_assert_equal_i(0, p_mkdir("etc", 0777)); + cl_git_pass(git_path_prettify(&path, "etc", NULL)); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, NULL)); - git_buf_free(&path); } @@ -26,6 +29,11 @@ void test_config_global__cleanup(void) { cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, NULL); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, NULL); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL); } void test_config_global__open_global(void) @@ -48,10 +56,7 @@ void test_config_global__open_xdg(void) const char *val, *str = "teststring"; const char *key = "this.variable"; - p_setenv("XDG_CONFIG_HOME", "xdg", 1); - - cl_must_pass(p_mkdir("xdg/git/", 0777)); - cl_git_mkfile("xdg/git/config", ""); + cl_git_mkfile("xdg/git/config", "# XDG config\n[core]\n test = 1\n"); cl_git_pass(git_config_open_default(&cfg)); cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG)); diff --git a/tests/config/include.c b/tests/config/include.c new file mode 100644 index 000000000..535573808 --- /dev/null +++ b/tests/config/include.c @@ -0,0 +1,109 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "fileops.h" + +void test_config_include__relative(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include"))); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "huzzah"); + + git_config_free(cfg); +} + +void test_config_include__absolute(void) +{ + git_config *cfg; + const char *str; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config"))); + + cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf)); + git_buf_free(&buf); + cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute")); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "huzzah"); + + git_config_free(cfg); +} + +void test_config_include__homedir(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); + cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included"); + + cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir")); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "huzzah"); + + git_config_free(cfg); +} + +void test_config_include__refresh(void) +{ + git_config *cfg; + const char *str; + + cl_fixture_sandbox("config"); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config-include")); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "huzzah"); + + /* Change the included file and see if we refresh */ + cl_git_mkfile("config/config-included", "[foo \"bar\"]\nbaz = hurrah"); + cl_git_pass(git_config_refresh(cfg)); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "hurrah"); + + git_config_free(cfg); + cl_fixture_cleanup("config"); +} + +/* We need to pretend that the variables were defined where the file was included */ +void test_config_include__ordering(void) +{ + git_config *cfg; + const char *str; + + cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya"); + cl_git_mkfile("including", + "[foo \"bar\"]\nfrotz = hello\n" + "[include]\npath = included\n" + "[foo \"bar\"]\nbaz = huzzah\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.frotz")); + cl_assert_equal_s(str, "hiya"); + cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz")); + cl_assert_equal_s(str, "huzzah"); + + git_config_free(cfg); +} + +/* We need to pretend that the variables were defined where the file was included */ +void test_config_include__depth(void) +{ + git_config *cfg; + + cl_git_mkfile("a", "[include]\npath = b"); + cl_git_mkfile("b", "[include]\npath = a"); + + cl_git_fail(git_config_open_ondisk(&cfg, "a")); + + unlink("a"); + unlink("b"); +} diff --git a/tests/config/multivar.c b/tests/config/multivar.c new file mode 100644 index 000000000..afdb1e5f4 --- /dev/null +++ b/tests/config/multivar.c @@ -0,0 +1,288 @@ +#include "clar_libgit2.h" + +static const char *_name = "remote.ab.url"; + +void test_config_multivar__initialize(void) +{ + cl_fixture_sandbox("config"); +} + +void test_config_multivar__cleanup(void) +{ + cl_fixture_cleanup("config"); +} + +static int mv_read_cb(const git_config_entry *entry, void *data) +{ + int *n = (int *) data; + + if (!strcmp(entry->name, _name)) + (*n)++; + + return 0; +} + +void test_config_multivar__foreach(void) +{ + git_config *cfg; + int n = 0; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11"))); + + cl_git_pass(git_config_foreach(cfg, mv_read_cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); +} + +static int cb(const git_config_entry *entry, void *data) +{ + int *n = (int *) data; + + GIT_UNUSED(entry); + + (*n)++; + + return 0; +} + +static void check_get_multivar_foreach( + git_config *cfg, int expected, int expected_patterned) +{ + int n = 0; + + if (expected > 0) { + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(expected, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + } + + n = 0; + + if (expected_patterned > 0) { + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); + cl_assert_equal_i(expected_patterned, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); + } +} + +static void check_get_multivar(git_config *cfg, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int n = 0; + + cl_git_pass(git_config_multivar_iterator_new(&iter, cfg, _name, NULL)); + + while (git_config_next(&entry, iter) == 0) + n++; + + cl_assert_equal_i(expected, n); + git_config_iterator_free(iter); + +} + +void test_config_multivar__get(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + check_get_multivar_foreach(cfg, 2, 1); + + /* add another that has the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* add another that does not have the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* add another that does not have the _name entry at the end */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* drop original file */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, 1)); + check_get_multivar_foreach(cfg, 1, 1); + + /* drop other file with match */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar_foreach(cfg, 0, 0); + + /* reload original file (add different place in order) */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1)); + check_get_multivar_foreach(cfg, 2, 1); + + check_get_multivar(cfg, 2); + + git_config_free(cfg); +} + +void test_config_multivar__add(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + cl_git_pass(git_config_set_multivar(cfg, _name, "nonexistant", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(n, 3); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); + + /* We know it works in memory, let's see if the file is written correctly */ + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(n, 3); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); +} + +void test_config_multivar__add_new(void) +{ + const char *var = "a.brand.new"; + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + cl_git_pass(git_config_set_multivar(cfg, var, "", "variable")); + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); +} + +void test_config_multivar__replace(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); +} + +void test_config_multivar__replace_multiple(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 2); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 2); + + git_config_free(cfg); +} + +void test_config_multivar__delete(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass(git_config_delete_multivar(cfg, _name, "github")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 1); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 1); + + git_config_free(cfg); +} + +void test_config_multivar__delete_multiple(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass(git_config_delete_multivar(cfg, _name, "git")); + + n = 0; + cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); + + git_config_free(cfg); +} + +void test_config_multivar__delete_notfound(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + cl_git_fail_with(git_config_delete_multivar(cfg, "remote.ab.noturl", "git"), GIT_ENOTFOUND); + + git_config_free(cfg); +} diff --git a/tests-clar/config/new.c b/tests/config/new.c similarity index 100% rename from tests-clar/config/new.c rename to tests/config/new.c diff --git a/tests-clar/config/read.c b/tests/config/read.c similarity index 79% rename from tests-clar/config/read.c rename to tests/config/read.c index 9f943d0f6..abc088d59 100644 --- a/tests-clar/config/read.c +++ b/tests/config/read.c @@ -164,6 +164,13 @@ void test_config_read__empty_files(void) git_config_free(cfg); } +void test_config_read__symbol_headers(void) +{ + git_config *cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config20"))); + git_config_free(cfg); +} + void test_config_read__header_in_last_line(void) { git_config *cfg; @@ -245,6 +252,37 @@ void test_config_read__foreach(void) git_config_free(cfg); } +void test_config_read__iterator(void) +{ + git_config *cfg; + git_config_iterator *iter; + git_config_entry *entry; + int count, ret; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + + count = 0; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + while ((ret = git_config_next(&entry, iter)) == 0) { + count++; + } + + git_config_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, ret); + cl_assert_equal_i(7, count); + + count = 3; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + git_config_iterator_free(iter); + git_config_free(cfg); +} + static int count_cfg_entries(const git_config_entry *entry, void *payload) { int *count = payload; @@ -288,6 +326,38 @@ void test_config_read__foreach_match(void) git_config_free(cfg); } +static void check_glob_iter(git_config *cfg, const char *regexp, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int count, error; + + cl_git_pass(git_config_iterator_glob_new(&iter, cfg, regexp)); + + count = 0; + while ((error = git_config_next(&entry, iter)) == 0) + count++; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected, count); + git_config_iterator_free(iter); +} + +void test_config_read__iterator_glob(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + check_glob_iter(cfg, "core.*", 3); + check_glob_iter(cfg, "remote\\.ab.*", 2); + check_glob_iter(cfg, ".*url$", 2); + check_glob_iter(cfg, ".*dummy.*", 2); + check_glob_iter(cfg, ".*nomatch.*", 0); + + git_config_free(cfg); +} + void test_config_read__whitespace_not_required_around_assignment(void) { git_config *cfg; @@ -431,10 +501,10 @@ void test_config_read__simple_read_from_specific_level(void) git_config_free(cfg); } -static void clean_empty_config(void *unused) +static void clean_test_config(void *unused) { GIT_UNUSED(unused); - cl_fixture_cleanup("./empty"); + cl_fixture_cleanup("./testconfig"); } void test_config_read__can_load_and_parse_an_empty_config_file(void) @@ -442,10 +512,58 @@ void test_config_read__can_load_and_parse_an_empty_config_file(void) git_config *cfg; int i; - cl_set_cleanup(&clean_empty_config, NULL); - cl_git_mkfile("./empty", ""); - cl_git_pass(git_config_open_ondisk(&cfg, "./empty")); + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", ""); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); git_config_free(cfg); } + +void test_config_read__corrupt_header(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[sneaky ] \"quoted closing quote mark\\\""); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__corrupt_header2(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[unclosed \"bracket\"\n lib = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__corrupt_header3(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[unclosed \"slash\\\"]\n lib = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__override_variable(void) +{ + git_config *cfg; + const char *str; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some] var = one\nvar = two"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_pass(git_config_get_string(&str, cfg, "some.var")); + cl_assert_equal_s(str, "two"); + + git_config_free(cfg); +} diff --git a/tests-clar/config/refresh.c b/tests/config/refresh.c similarity index 100% rename from tests-clar/config/refresh.c rename to tests/config/refresh.c diff --git a/tests-clar/config/stress.c b/tests/config/stress.c similarity index 95% rename from tests-clar/config/stress.c rename to tests/config/stress.c index 8cc64d23c..eeca54ff4 100644 --- a/tests-clar/config/stress.c +++ b/tests/config/stress.c @@ -10,12 +10,12 @@ void test_config_stress__initialize(void) { git_filebuf file = GIT_FILEBUF_INIT; - cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0)); + cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0, 0666)); git_filebuf_printf(&file, "[color]\n\tui = auto\n"); git_filebuf_printf(&file, "[core]\n\teditor = \n"); - cl_git_pass(git_filebuf_commit(&file, 0666)); + cl_git_pass(git_filebuf_commit(&file)); } void test_config_stress__cleanup(void) diff --git a/tests-clar/config/validkeyname.c b/tests/config/validkeyname.c similarity index 96% rename from tests-clar/config/validkeyname.c rename to tests/config/validkeyname.c index 03c13d723..33699737b 100644 --- a/tests-clar/config/validkeyname.c +++ b/tests/config/validkeyname.c @@ -28,7 +28,7 @@ static void assert_invalid_config_key_name(const char *name) GIT_EINVALIDSPEC); cl_git_fail_with(git_config_delete_entry(cfg, name), GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_get_multivar(cfg, name, "*", NULL, NULL), + cl_git_fail_with(git_config_get_multivar_foreach(cfg, name, "*", NULL, NULL), GIT_EINVALIDSPEC); cl_git_fail_with(git_config_set_multivar(cfg, name, "*", "42"), GIT_EINVALIDSPEC); diff --git a/tests-clar/config/write.c b/tests/config/write.c similarity index 78% rename from tests-clar/config/write.c rename to tests/config/write.c index d70612a97..15f750dc0 100644 --- a/tests-clar/config/write.c +++ b/tests/config/write.c @@ -229,6 +229,37 @@ void test_config_write__add_value_at_file_with_no_clrf_at_the_end(void) git_config_free(cfg); } +void test_config_write__add_value_which_needs_quotes(void) +{ + git_config *cfg; + const char* str1; + const char* str2; + const char* str3; + const char* str4; + const char* str5; + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_set_string(cfg, "core.startwithspace", " Something")); + cl_git_pass(git_config_set_string(cfg, "core.endwithspace", "Something ")); + cl_git_pass(git_config_set_string(cfg, "core.containscommentchar1", "some#thing")); + cl_git_pass(git_config_set_string(cfg, "core.containscommentchar2", "some;thing")); + cl_git_pass(git_config_set_string(cfg, "core.startwhithsapceandcontainsdoublequote", " some\"thing")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_get_string(&str1, cfg, "core.startwithspace")); + cl_assert_equal_s(" Something", str1); + cl_git_pass(git_config_get_string(&str2, cfg, "core.endwithspace")); + cl_assert_equal_s("Something ", str2); + cl_git_pass(git_config_get_string(&str3, cfg, "core.containscommentchar1")); + cl_assert_equal_s("some#thing", str3); + cl_git_pass(git_config_get_string(&str4, cfg, "core.containscommentchar2")); + cl_assert_equal_s("some;thing", str4); + cl_git_pass(git_config_get_string(&str5, cfg, "core.startwhithsapceandcontainsdoublequote")); + cl_assert_equal_s(" some\"thing", str5); + git_config_free(cfg); +} + void test_config_write__can_set_a_value_to_NULL(void) { git_repository *repository; @@ -242,3 +273,33 @@ void test_config_write__can_set_a_value_to_NULL(void) cl_git_sandbox_cleanup(); } + +void test_config_write__can_set_an_empty_value(void) +{ + git_repository *repository; + git_config *config; + const char * str; + + repository = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_config(&config, repository)); + + cl_git_pass(git_config_set_string(config, "core.somevar", "")); + cl_git_pass(git_config_get_string(&str, config, "core.somevar")); + cl_assert_equal_s(str, ""); + + git_config_free(config); + cl_git_sandbox_cleanup(); +} + +void test_config_write__updating_a_locked_config_file_returns_ELOCKED(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + + cl_git_mkfile("config9.lock", "[core]\n"); + + cl_git_fail_with(git_config_set_string(cfg, "core.dump", "boom"), GIT_ELOCKED); + + git_config_free(cfg); +} diff --git a/tests/core/bitvec.c b/tests/core/bitvec.c new file mode 100644 index 000000000..48d7b99f0 --- /dev/null +++ b/tests/core/bitvec.c @@ -0,0 +1,64 @@ +#include "clar_libgit2.h" +#include "bitvec.h" + +#if 0 +static void print_bitvec(git_bitvec *bv) +{ + int b; + + if (!bv->length) { + for (b = 63; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); + } else { + for (b = bv->length * 8; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); + } + fprintf(stderr, "\n"); +} +#endif + +static void set_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) { + if (i % 3 == 0 || i % 7 == 0) + git_bitvec_set(bv, i, true); + } +} + +static void check_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) + cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); +} + +void test_core_bitvec__0(void) +{ + git_bitvec bv; + + cl_git_pass(git_bitvec_init(&bv, 32)); + set_some_bits(&bv, 16); + check_some_bits(&bv, 16); + git_bitvec_clear(&bv); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + git_bitvec_clear(&bv); + set_some_bits(&bv, 64); + check_some_bits(&bv, 64); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 128)); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + set_some_bits(&bv, 128); + check_some_bits(&bv, 128); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 4000)); + set_some_bits(&bv, 4000); + check_some_bits(&bv, 4000); + git_bitvec_free(&bv); +} diff --git a/tests-clar/core/buffer.c b/tests/core/buffer.c similarity index 90% rename from tests-clar/core/buffer.c rename to tests/core/buffer.c index 3d8221e04..11d173d49 100644 --- a/tests-clar/core/buffer.c +++ b/tests/core/buffer.c @@ -718,6 +718,8 @@ void test_core_buffer__classify_with_utf8(void) size_t data1len = 31; char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; size_t data2len = 29; + char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; + size_t data3len = 20; git_buf b; b.ptr = data0; b.size = b.asize = data0len; @@ -731,13 +733,18 @@ void test_core_buffer__classify_with_utf8(void) b.ptr = data2; b.size = b.asize = data2len; cl_assert(git_buf_text_is_binary(&b)); cl_assert(git_buf_text_contains_nul(&b)); + + b.ptr = data3; b.size = b.asize = data3len; + cl_assert(!git_buf_text_is_binary(&b)); + cl_assert(!git_buf_text_contains_nul(&b)); } #define SIMILARITY_TEST_DATA_1 \ - "test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" \ - "is this enough?\nthere has to be enough data to fill the hash array!\n" \ - "Apparently 191 bytes is the minimum amount of data needed.\nHere goes!\n" \ - "Let's make sure we've got plenty to go with here.\n smile \n" + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" void test_core_buffer__similarity_metric(void) { @@ -761,15 +768,17 @@ 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_buf_sets(&buf, - "Test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" - "is this enough?\nthere has to be enough data to fill the hash array!\n" - "Apparently 191 bytes is the minimum amount of data needed.\nHere goes!\n" - "Let's make sure we've got plenty to go with here.\n smile \n")); + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + )); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); - cl_assert(95 < sim && sim < 100); /* expect >95% similarity */ + cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ git_hashsig_free(a); git_hashsig_free(b); @@ -779,12 +788,13 @@ 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_buf_sets(&buf, SIMILARITY_TEST_DATA_1 - "and if I add some more, it should still be pretty similar, yes?\n")); + "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); + /* 20% lines added ~= 10% lines changed */ - cl_assert(70 < sim && sim < 80); /* expect in the 70-80% similarity range */ + cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ git_hashsig_free(a); git_hashsig_free(b); @@ -794,15 +804,19 @@ 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_buf_sets(&buf, - "test data\nright here\ninline\ntada\nneeds more data\nlots of data\n" - "is this enough?\nthere has to be enough data to fill the hash array!\n" - "okay, that's half the original\nwhat else can we add?\nmore data\n" - "one more line will complete this\nshort\nlines\ndon't\nmatter\n")); + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020x\n021\n022\n023\n024\n" \ + "x25\nx26\nx27\nx28\nx29\n" \ + "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ + "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" + )); cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); sim = git_hashsig_compare(a, b); + /* 50% lines changed */ - cl_assert(40 < sim && sim < 60); /* expect in the 40-60% similarity range */ + cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ git_hashsig_free(a); git_hashsig_free(b); @@ -891,7 +905,7 @@ void test_core_buffer__similarity_metric_whitespace(void) if (i == j) cl_assert_equal_i(100, sim); else - cl_assert(sim < 30); /* expect pretty different */ + cl_assert_in_range(0, sim, 30); /* pretty different */ } else { cl_assert_equal_i(100, sim); } @@ -905,6 +919,8 @@ void test_core_buffer__similarity_metric_whitespace(void) git_buf_free(&buf); } +#include "../filter/crlf.h" + #define check_buf(expected,buf) do { \ cl_assert_equal_s(expected, buf.ptr); \ cl_assert_equal_sz(strlen(expected), buf.size); } while (0) @@ -920,16 +936,16 @@ void test_core_buffer__lf_and_crlf_conversions(void) cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt); - cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src)); - /* no conversion needed if all LFs already */ + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); git_buf_sets(&src, "\nlf\nlf\nlf\nlf\nlf"); cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt); - cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src)); - /* no conversion needed if all LFs already */ + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); /* CRLF source */ @@ -979,10 +995,45 @@ void test_core_buffer__lf_and_crlf_conversions(void) check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt); git_buf_sets(&src, "\rcr\r"); - cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_lf_to_crlf(&tgt, &src)); + cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); + check_buf(src.ptr, tgt); cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); check_buf("\rcr\r", tgt); git_buf_free(&src); git_buf_free(&tgt); + + /* blob correspondence tests */ + + git_buf_sets(&src, ALL_CRLF_TEXT_RAW); + cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt); + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_LF, tgt); + git_buf_free(&src); + git_buf_free(&tgt); + + git_buf_sets(&src, ALL_LF_TEXT_RAW); + cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_CRLF, tgt); + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_LF, tgt); + git_buf_free(&src); + git_buf_free(&tgt); + + git_buf_sets(&src, MORE_CRLF_TEXT_RAW); + cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt); + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_LF, tgt); + git_buf_free(&src); + git_buf_free(&tgt); + + git_buf_sets(&src, MORE_LF_TEXT_RAW); + cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_CRLF, tgt); + cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_LF, tgt); + git_buf_free(&src); + git_buf_free(&tgt); } diff --git a/tests/core/caps.c b/tests/core/caps.c new file mode 100644 index 000000000..68a518ed7 --- /dev/null +++ b/tests/core/caps.c @@ -0,0 +1,31 @@ +#include "clar_libgit2.h" + +void test_core_caps__0(void) +{ + int major, minor, rev, caps; + + git_libgit2_version(&major, &minor, &rev); + cl_assert_equal_i(LIBGIT2_VER_MAJOR, major); + cl_assert_equal_i(LIBGIT2_VER_MINOR, minor); + cl_assert_equal_i(LIBGIT2_VER_REVISION, rev); + + caps = git_libgit2_capabilities(); + +#ifdef GIT_THREADS + cl_assert((caps & GIT_CAP_THREADS) != 0); +#else + cl_assert((caps & GIT_CAP_THREADS) == 0); +#endif + +#if defined(GIT_SSL) || defined(GIT_WINHTTP) + cl_assert((caps & GIT_CAP_HTTPS) != 0); +#else + cl_assert((caps & GIT_CAP_HTTPS) == 0); +#endif + +#if defined(GIT_SSH) + cl_assert((caps & GIT_CAP_SSH) != 0); +#else + cl_assert((caps & GIT_CAP_SSH) == 0); +#endif +} diff --git a/tests-clar/core/copy.c b/tests/core/copy.c similarity index 100% rename from tests-clar/core/copy.c rename to tests/core/copy.c diff --git a/tests-clar/core/dirent.c b/tests/core/dirent.c similarity index 78% rename from tests-clar/core/dirent.c rename to tests/core/dirent.c index 5a7859d1b..f17260362 100644 --- a/tests-clar/core/dirent.c +++ b/tests/core/dirent.c @@ -88,14 +88,6 @@ static int one_entry(void *state, git_buf *path) return GIT_ERROR; } -static int dont_call_me(void *state, git_buf *path) -{ - GIT_UNUSED(state); - GIT_UNUSED(path); - return GIT_ERROR; -} - - static name_data dot_names[] = { { 0, "./a" }, @@ -115,9 +107,7 @@ void test_core_dirent__dont_traverse_dot(void) cl_set_cleanup(&dirent_cleanup__cb, &dot); setup(&dot); - cl_git_pass(git_path_direach(&dot.path, - one_entry, - &dot)); + cl_git_pass(git_path_direach(&dot.path, 0, one_entry, &dot)); check_counts(&dot); } @@ -141,9 +131,7 @@ void test_core_dirent__traverse_subfolder(void) cl_set_cleanup(&dirent_cleanup__cb, &sub); setup(&sub); - cl_git_pass(git_path_direach(&sub.path, - one_entry, - &sub)); + cl_git_pass(git_path_direach(&sub.path, 0, one_entry, &sub)); check_counts(&sub); } @@ -161,9 +149,7 @@ void test_core_dirent__traverse_slash_terminated_folder(void) cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); setup(&sub_slash); - cl_git_pass(git_path_direach(&sub_slash.path, - one_entry, - &sub_slash)); + cl_git_pass(git_path_direach(&sub_slash.path, 0, one_entry, &sub_slash)); check_counts(&sub_slash); } @@ -184,16 +170,12 @@ void test_core_dirent__dont_traverse_empty_folders(void) cl_set_cleanup(&dirent_cleanup__cb, &empty); setup(&empty); - cl_git_pass(git_path_direach(&empty.path, - one_entry, - &empty)); + cl_git_pass(git_path_direach(&empty.path, 0, one_entry, &empty)); check_counts(&empty); /* make sure callback not called */ - cl_git_pass(git_path_direach(&empty.path, - dont_call_me, - &empty)); + cl_assert(git_path_is_empty_dir(empty.path.ptr)); } static name_data odd_names[] = { @@ -216,9 +198,7 @@ void test_core_dirent__traverse_weird_filenames(void) cl_set_cleanup(&dirent_cleanup__cb, &odd); setup(&odd); - cl_git_pass(git_path_direach(&odd.path, - one_entry, - &odd)); + cl_git_pass(git_path_direach(&odd.path, 0, one_entry, &odd)); check_counts(&odd); } @@ -231,5 +211,26 @@ void test_core_dirent__length_limits(void) big_filename[FILENAME_MAX] = 0; cl_must_fail(p_creat(big_filename, 0666)); + git__free(big_filename); } + +void test_core_dirent__empty_dir(void) +{ + cl_must_pass(p_mkdir("empty_dir", 0777)); + cl_assert(git_path_is_empty_dir("empty_dir")); + + cl_git_mkfile("empty_dir/content", "whatever\n"); + cl_assert(!git_path_is_empty_dir("empty_dir")); + cl_assert(!git_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_unlink("empty_dir/content")); + + cl_must_pass(p_mkdir("empty_dir/content", 0777)); + cl_assert(!git_path_is_empty_dir("empty_dir")); + cl_assert(git_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir")); +} diff --git a/tests-clar/core/env.c b/tests/core/env.c similarity index 100% rename from tests-clar/core/env.c rename to tests/core/env.c diff --git a/tests-clar/core/errors.c b/tests/core/errors.c similarity index 100% rename from tests-clar/core/errors.c rename to tests/core/errors.c diff --git a/tests-clar/core/filebuf.c b/tests/core/filebuf.c similarity index 55% rename from tests-clar/core/filebuf.c rename to tests/core/filebuf.c index 4451c01c7..5a3e7510f 100644 --- a/tests-clar/core/filebuf.c +++ b/tests/core/filebuf.c @@ -13,7 +13,7 @@ void test_core_filebuf__0(void) cl_must_pass(fd); cl_must_pass(p_close(fd)); - cl_git_fail(git_filebuf_open(&file, test, 0)); + cl_git_fail(git_filebuf_open(&file, test, 0, 0666)); cl_assert(git_path_exists(testlock)); cl_must_pass(p_unlink(testlock)); @@ -24,17 +24,15 @@ void test_core_filebuf__0(void) void test_core_filebuf__1(void) { git_filebuf file = GIT_FILEBUF_INIT; - int fd; char test[] = "test"; - fd = p_creat(test, 0666); //-V536 - cl_must_pass(fd); - cl_must_pass(p_write(fd, "libgit2 rocks\n", 14)); - cl_must_pass(p_close(fd)); + cl_git_mkfile(test, "libgit2 rocks\n"); - cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND)); + cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666)); cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_git_pass(git_filebuf_commit(&file, 0666)); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test); cl_must_pass(p_unlink(test)); } @@ -49,9 +47,11 @@ void test_core_filebuf__2(void) memset(buf, 0xfe, sizeof(buf)); - cl_git_pass(git_filebuf_open(&file, test, 0)); + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); - cl_git_pass(git_filebuf_commit(&file, 0666)); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file((char *)buf, sizeof(buf), test); cl_must_pass(p_unlink(test)); } @@ -64,7 +64,7 @@ void test_core_filebuf__4(void) cl_assert(file.buffer == NULL); - cl_git_pass(git_filebuf_open(&file, test, 0)); + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); cl_assert(file.buffer != NULL); git_filebuf_cleanup(&file); @@ -80,13 +80,47 @@ void test_core_filebuf__5(void) cl_assert(file.buffer == NULL); - cl_git_pass(git_filebuf_open(&file, test, 0)); + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); cl_assert(file.buffer != NULL); cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); cl_assert(file.buffer != NULL); - cl_git_pass(git_filebuf_commit(&file, 0666)); + cl_git_pass(git_filebuf_commit(&file)); cl_assert(file.buffer == NULL); cl_must_pass(p_unlink(test)); } + + +/* make sure git_filebuf_commit takes umask into account */ +void test_core_filebuf__umask(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + struct stat statbuf; + mode_t mask, os_mask; + +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + p_umask(mask = p_umask(0)); + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_assert(file.buffer != NULL); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert(file.buffer == NULL); + + cl_must_pass(p_stat("test", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask); + + cl_must_pass(p_unlink(test)); +} + diff --git a/tests-clar/core/hex.c b/tests/core/hex.c similarity index 100% rename from tests-clar/core/hex.c rename to tests/core/hex.c diff --git a/tests/core/iconv.c b/tests/core/iconv.c new file mode 100644 index 000000000..8aedab206 --- /dev/null +++ b/tests/core/iconv.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "path.h" + +#ifdef GIT_USE_ICONV +static git_path_iconv_t ic; +static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; +#endif + +void test_core_iconv__initialize(void) +{ +#ifdef GIT_USE_ICONV + cl_git_pass(git_path_iconv_init_precompose(&ic)); +#endif +} + +void test_core_iconv__cleanup(void) +{ +#ifdef GIT_USE_ICONV + git_path_iconv_clear(&ic); +#endif +} + +void test_core_iconv__unchanged(void) +{ +#ifdef GIT_USE_ICONV + char *data = "Ascii data", *original = data; + size_t datalen = strlen(data); + + cl_git_pass(git_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* There are no high bits set, so this should leave data untouched */ + cl_assert(data == original); +#endif +} + +void test_core_iconv__decomposed_to_precomposed(void) +{ +#ifdef GIT_USE_ICONV + char *data = nfd; + size_t datalen = strlen(nfd); + + cl_git_pass(git_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* The decomposed nfd string should be transformed to the nfc form + * (on platforms where iconv is enabled, of course). + */ + cl_assert_equal_s(nfc, data); +#endif +} + +void test_core_iconv__precomposed_is_unmodified(void) +{ +#ifdef GIT_USE_ICONV + char *data = nfc; + size_t datalen = strlen(nfc); + + cl_git_pass(git_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* data is already in precomposed form, so even though some bytes have + * the high-bit set, the iconv transform should result in no change. + */ + cl_assert_equal_s(nfc, data); +#endif +} diff --git a/tests-clar/core/mkdir.c b/tests/core/mkdir.c similarity index 93% rename from tests-clar/core/mkdir.c rename to tests/core/mkdir.c index 1e50b4336..a8c5b10ae 100644 --- a/tests-clar/core/mkdir.c +++ b/tests/core/mkdir.c @@ -111,14 +111,20 @@ static void cleanup_chmod_root(void *ref) git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } -static void check_mode(mode_t expected, mode_t actual) +#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __LINE__) + +static void check_mode_at_line( + mode_t expected, mode_t actual, const char *file, int line) { -#ifdef GIT_WIN32 - /* chmod on Win32 doesn't support exec bit, not group/world bits */ - cl_assert((expected & 0600) == (actual & 0777)); -#else - cl_assert(expected == (actual & 0777)); -#endif + /* FAT filesystems don't support exec bit, nor group/world bits */ + if (!cl_is_chmod_supported()) { + expected &= 0600; + actual &= 0600; + } + + clar__assert_equal( + file, line, "expected_mode != actual_mode", 1, + "%07o", (int)expected, (int)(actual & 0777)); } void test_core_mkdir__chmods(void) diff --git a/tests-clar/core/oid.c b/tests/core/oid.c similarity index 100% rename from tests-clar/core/oid.c rename to tests/core/oid.c diff --git a/tests-clar/core/oidmap.c b/tests/core/oidmap.c similarity index 100% rename from tests-clar/core/oidmap.c rename to tests/core/oidmap.c diff --git a/tests-clar/core/opts.c b/tests/core/opts.c similarity index 100% rename from tests-clar/core/opts.c rename to tests/core/opts.c diff --git a/tests-clar/core/path.c b/tests/core/path.c similarity index 77% rename from tests-clar/core/path.c rename to tests/core/path.c index 407770baa..cf2d5e944 100644 --- a/tests-clar/core/path.c +++ b/tests/core/path.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include +#include "fileops.h" static void check_dirname(const char *A, const char *B) @@ -446,16 +446,15 @@ void test_core_path__14_apply_relative(void) cl_git_pass(git_path_apply_relative(&p, "../../../../../..")); cl_assert_equal_s("/this/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_git_pass(git_path_apply_relative(&p, "../")); cl_assert_equal_s("/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../..")); - cl_assert_equal_s("/", p.ptr); + cl_git_fail(git_path_apply_relative(&p, "../../..")); cl_git_pass(git_buf_sets(&p, "d:/another/test")); - cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_git_pass(git_path_apply_relative(&p, "../..")); cl_assert_equal_s("d:/", p.ptr); cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/.")); @@ -473,8 +472,112 @@ void test_core_path__14_apply_relative(void) cl_git_pass(git_path_apply_relative(&p, "..")); cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_git_pass(git_path_apply_relative(&p, "../../../")); cl_assert_equal_s("https://", p.ptr); + + cl_git_pass(git_buf_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); git_buf_free(&p); } + +static void assert_resolve_relative( + git_buf *buf, const char *expected, const char *path) +{ + cl_git_pass(git_buf_sets(buf, path)); + cl_git_pass(git_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_core_path__15_resolve_relative(void) +{ + git_buf buf = GIT_BUF_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_buf_sets(&buf, "/..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/./..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/.//..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/../.")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "/../.././../a")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_buf_sets(&buf, "////..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); + + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_buf_sets(&buf, "//a/b/../..")); + cl_git_fail(git_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + + git_buf_free(&buf); +} diff --git a/tests-clar/core/pool.c b/tests/core/pool.c similarity index 100% rename from tests-clar/core/pool.c rename to tests/core/pool.c diff --git a/tests/core/posix.c b/tests/core/posix.c new file mode 100644 index 000000000..1cef937cd --- /dev/null +++ b/tests/core/posix.c @@ -0,0 +1,99 @@ +#ifndef _WIN32 +# include +# include +# include +#else +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#include "clar_libgit2.h" +#include "posix.h" + +void test_core_posix__initialize(void) +{ +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); + cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); +#endif +} + +static bool supports_ipv6(void) +{ +#ifdef GIT_WIN32 + /* IPv6 is supported on Vista and newer */ + return git_has_win32_version(6, 0, 0); +#else + return 1; +#endif +} + +void test_core_posix__inet_pton(void) +{ + struct in_addr addr; + struct in6_addr addr6; + size_t i; + + struct in_addr_data { + const char *p; + const uint8_t n[4]; + }; + + struct in6_addr_data { + const char *p; + const uint8_t n[16]; + }; + + static struct in_addr_data in_addr_data[] = { + { "0.0.0.0", { 0, 0, 0, 0 } }, + { "10.42.101.8", { 10, 42, 101, 8 } }, + { "127.0.0.1", { 127, 0, 0, 1 } }, + { "140.177.10.12", { 140, 177, 10, 12 } }, + { "204.232.175.90", { 204, 232, 175, 90 } }, + { "255.255.255.255", { 255, 255, 255, 255 } }, + }; + + static struct in6_addr_data in6_addr_data[] = { + { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, + { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, + { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, + }; + + /* Test some ipv4 addresses */ + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); + cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); + } + + /* Test some ipv6 addresses */ + if (supports_ipv6()) + { + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); + cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + } + } + + /* Test some invalid strings */ + cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); + + /* Test unsupported address families */ + cl_git_fail(p_inet_pton(12, "52.472", NULL)); /* AF_DECnet */ + cl_assert_equal_i(EAFNOSUPPORT, errno); + + cl_git_fail(p_inet_pton(5, "315.124", NULL)); /* AF_CHAOS */ + cl_assert_equal_i(EAFNOSUPPORT, errno); +} diff --git a/tests-clar/core/rmdir.c b/tests/core/rmdir.c similarity index 100% rename from tests-clar/core/rmdir.c rename to tests/core/rmdir.c diff --git a/tests/core/sortedcache.c b/tests/core/sortedcache.c new file mode 100644 index 000000000..c1869bee0 --- /dev/null +++ b/tests/core/sortedcache.c @@ -0,0 +1,363 @@ +#include "clar_libgit2.h" +#include "sortedcache.h" + +static int name_only_cmp(const void *a, const void *b) +{ + return strcmp(a, b); +} + +void test_core_sortedcache__name_only(void) +{ + git_sortedcache *sc; + void *item; + size_t pos; + + cl_git_pass(git_sortedcache_new( + &sc, 0, NULL, NULL, name_only_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(4, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); + + git_sortedcache_clear(sc, true); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + git_sortedcache_free(sc); +} + +typedef struct { + int value; + char smaller_value; + char path[GIT_FLEX_ARRAY]; +} sortedcache_test_struct; + +static int sortedcache_test_struct_cmp(const void *a_, const void *b_) +{ + const sortedcache_test_struct *a = a_, *b = b_; + return strcmp(a->path, b->path); +} + +static void sortedcache_test_struct_free(void *payload, void *item_) +{ + sortedcache_test_struct *item = item_; + int *count = payload; + (*count)++; + item->smaller_value = 0; +} + +void test_core_sortedcache__in_memory(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); + item->value = 10; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); + item->value = 20; + item->smaller_value = 2; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); + item->value = 30; + item->smaller_value = 26; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); + item->value = 40; + item->smaller_value = 14; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); + item->value = 50; + item->smaller_value = 9; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_rlock(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); + + /* not on Windows: + * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one + */ + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item->path); + cl_assert_equal_i(50, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + git_sortedcache_runlock(sc); + /* git_sortedcache_runlock(sc); */ + + cl_assert_equal_i(0, free_count); + + git_sortedcache_clear(sc, true); + + cl_assert_equal_i(5, free_count); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + free_count = 0; + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); + item->value = 10; + item->smaller_value = 3; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); + item->value = 20; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); + item->value = 30; + item->smaller_value = 2; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + { + size_t pos; + + cl_git_pass(git_sortedcache_wlock(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); + + cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); + + cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + + git_sortedcache_wunlock(sc); + } + + git_sortedcache_free(sc); + + cl_assert_equal_i(3, free_count); +} + +static void sortedcache_test_reload(git_sortedcache *sc) +{ + int count = 0; + git_buf buf = GIT_BUF_INIT; + char *scan, *after; + sortedcache_test_struct *item; + + cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); + + git_sortedcache_clear(sc, false); /* clear once we already have lock */ + + for (scan = buf.ptr; *scan; scan = after + 1) { + int val = strtol(scan, &after, 0); + cl_assert(after > scan); + scan = after; + + for (scan = after; git__isspace(*scan); ++scan) /* find start */; + for (after = scan; *after && *after != '\n'; ++after) /* find eol */; + *after = '\0'; + + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); + + item->value = val; + item->smaller_value = (char)(count++); + } + + git_sortedcache_wunlock(sc); + + git_buf_free(&buf); +} + +void test_core_sortedcache__on_disk(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + size_t pos; + + cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, "cacheitems.txt")); + + /* should need to reload the first time */ + + sortedcache_test_reload(sc); + + /* test what we loaded */ + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); + cl_assert_equal_s("cde", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bcd", item->path); + cl_assert_equal_i(20, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + /* should not need to reload this time */ + + cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); + + /* rewrite ondisk file and reload */ + + cl_assert_equal_i(0, free_count); + + cl_git_rewritefile( + "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); + sortedcache_test_reload(sc); + + cl_assert_equal_i(3, free_count); + + /* test what we loaded */ + + cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(100, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(500, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(200, item->value); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(3, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); + + git_sortedcache_free(sc); + + cl_assert_equal_i(7, free_count); +} + diff --git a/tests-clar/core/stat.c b/tests/core/stat.c similarity index 100% rename from tests-clar/core/stat.c rename to tests/core/stat.c diff --git a/tests-clar/core/string.c b/tests/core/string.c similarity index 100% rename from tests-clar/core/string.c rename to tests/core/string.c diff --git a/tests-clar/core/strmap.c b/tests/core/strmap.c similarity index 100% rename from tests-clar/core/strmap.c rename to tests/core/strmap.c diff --git a/tests-clar/core/strtol.c b/tests/core/strtol.c similarity index 100% rename from tests-clar/core/strtol.c rename to tests/core/strtol.c diff --git a/tests-clar/core/vector.c b/tests/core/vector.c similarity index 99% rename from tests-clar/core/vector.c rename to tests/core/vector.c index c9e43a149..db52c004f 100644 --- a/tests-clar/core/vector.c +++ b/tests/core/vector.c @@ -54,7 +54,7 @@ void test_core_vector__2(void) cl_git_pass(git_vector_insert(&x, ptrs[1])); cl_assert(x.length == 5); - git_vector_uniq(&x); + git_vector_uniq(&x, NULL); cl_assert(x.length == 2); git_vector_free(&x); diff --git a/tests-clar/date/date.c b/tests/date/date.c similarity index 100% rename from tests-clar/date/date.c rename to tests/date/date.c diff --git a/tests-clar/diff/blob.c b/tests/diff/blob.c similarity index 83% rename from tests-clar/diff/blob.c rename to tests/diff/blob.c index 42b9fcd5f..93f20711c 100644 --- a/tests-clar/diff/blob.c +++ b/tests/diff/blob.c @@ -26,9 +26,8 @@ void test_diff_blob__initialize(void) g_repo = cl_git_sandbox_init("attr"); - GIT_INIT_STRUCTURE(&opts, GIT_DIFF_OPTIONS_VERSION); + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); opts.context_lines = 1; - opts.interhunk_lines = 0; memset(&expected, 0, sizeof(expected)); @@ -142,7 +141,7 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) { git_blob *a, *b, *c; git_oid a_oid, b_oid, c_oid; - git_diff_patch *p; + git_patch *p; const git_diff_delta *delta; size_t tc, ta, td; @@ -161,11 +160,11 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) /* Doing the equivalent of a `git diff -U1` on these files */ /* diff on tests/resources/attr/root_test1 */ - cl_git_pass(git_diff_patch_from_blobs(&p, a, NULL, b, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.oid)); @@ -173,22 +172,22 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) cl_assert(git_oid_equal(git_blob_id(b), &delta->new_file.oid)); cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(6, git_diff_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0)); - cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(1, (int)tc); cl_assert_equal_i(5, (int)ta); cl_assert_equal_i(0, (int)td); - git_diff_patch_free(p); + git_patch_free(p); /* diff on tests/resources/attr/root_test2 */ - cl_git_pass(git_diff_patch_from_blobs(&p, b, NULL, c, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, b, NULL, c, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); cl_assert(git_oid_equal(git_blob_id(b), &delta->old_file.oid)); @@ -196,22 +195,22 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.oid)); cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(15, git_diff_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(15, git_patch_num_lines_in_hunk(p, 0)); - cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(3, (int)tc); cl_assert_equal_i(9, (int)ta); cl_assert_equal_i(3, (int)td); - git_diff_patch_free(p); + git_patch_free(p); /* diff on tests/resources/attr/root_test3 */ - cl_git_pass(git_diff_patch_from_blobs(&p, a, NULL, c, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, a, NULL, c, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.oid)); @@ -219,19 +218,19 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.oid)); cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size); - cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(0, (int)tc); cl_assert_equal_i(12, (int)ta); cl_assert_equal_i(1, (int)td); - git_diff_patch_free(p); + git_patch_free(p); /* one more */ - cl_git_pass(git_diff_patch_from_blobs(&p, c, NULL, d, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, c, NULL, d, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); cl_assert(git_oid_equal(git_blob_id(c), &delta->old_file.oid)); @@ -239,16 +238,16 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void) cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); - cl_assert_equal_i(2, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(5, git_diff_patch_num_lines_in_hunk(p, 0)); - cl_assert_equal_i(9, git_diff_patch_num_lines_in_hunk(p, 1)); + cl_assert_equal_i(2, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(5, git_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(9, git_patch_num_lines_in_hunk(p, 1)); - cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(4, (int)tc); cl_assert_equal_i(6, (int)ta); cl_assert_equal_i(4, (int)td); - git_diff_patch_free(p); + git_patch_free(p); git_blob_free(a); git_blob_free(b); @@ -317,16 +316,16 @@ void test_diff_blob__can_compare_against_null_blobs(void) void test_diff_blob__can_compare_against_null_blobs_with_patch(void) { git_blob *e = NULL; - git_diff_patch *p; + git_patch *p; const git_diff_delta *delta; - int line; - char origin; + const git_diff_line *line; + int l, max_l; - cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); cl_assert(git_oid_equal(git_blob_id(d), &delta->old_file.oid)); @@ -334,24 +333,24 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) cl_assert(git_oid_iszero(&delta->new_file.oid)); cl_assert_equal_sz(0, delta->new_file.size); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); - for (line = 0; line < git_diff_patch_num_lines_in_hunk(p, 0); ++line) { - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, NULL, NULL, NULL, NULL, p, 0, line)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)origin); + max_l = git_patch_num_lines_in_hunk(p, 0); + for (l = 0; l < max_l; ++l) { + cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); } - git_diff_patch_free(p); + git_patch_free(p); opts.flags |= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); cl_assert(git_oid_iszero(&delta->old_file.oid)); @@ -359,44 +358,44 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); - for (line = 0; line < git_diff_patch_num_lines_in_hunk(p, 0); ++line) { - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, NULL, NULL, NULL, NULL, p, 0, line)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)origin); + max_l = git_patch_num_lines_in_hunk(p, 0); + for (l = 0; l < max_l; ++l) { + cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); } - git_diff_patch_free(p); + git_patch_free(p); opts.flags ^= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_diff_patch_free(p); + git_patch_free(p); } static void assert_identical_blobs_comparison(diff_expects *expected) @@ -437,13 +436,13 @@ void test_diff_blob__can_compare_identical_blobs(void) void test_diff_blob__can_compare_identical_blobs_with_patch(void) { - git_diff_patch *p; + git_patch *p; const git_diff_delta *delta; - cl_git_pass(git_diff_patch_from_blobs(&p, d, NULL, d, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, d, NULL, d, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); cl_assert_equal_sz(delta->old_file.size, git_blob_rawsize(d)); @@ -451,13 +450,13 @@ void test_diff_blob__can_compare_identical_blobs_with_patch(void) cl_assert_equal_sz(delta->new_file.size, git_blob_rawsize(d)); cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.oid)); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); - git_diff_patch_free(p); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts)); cl_assert(p != NULL); - delta = git_diff_patch_delta(p); + delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); cl_assert_equal_sz(0, delta->old_file.size); @@ -465,14 +464,14 @@ void test_diff_blob__can_compare_identical_blobs_with_patch(void) cl_assert_equal_sz(0, delta->new_file.size); cl_assert(git_oid_iszero(&delta->new_file.oid)); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); - git_diff_patch_free(p); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts)); + cl_git_pass(git_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); - git_diff_patch_free(p); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); } static void assert_binary_blobs_comparison(diff_expects *expected) @@ -693,7 +692,7 @@ void test_diff_blob__can_compare_blob_to_buffer(void) void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) { - git_diff_patch *p; + git_patch *p; git_blob *a; git_oid a_oid; const char *a_content = "Hello from the root\n"; @@ -705,58 +704,58 @@ void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); /* diff from blob a to content of b */ - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, a, NULL, b_content, strlen(b_content), NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(6, git_diff_patch_num_lines_in_hunk(p, 0)); + cl_assert_equal_i(GIT_DELTA_MODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0)); - cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p)); + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); cl_assert_equal_i(1, (int)tc); cl_assert_equal_i(5, (int)ta); cl_assert_equal_i(0, (int)td); - git_diff_patch_free(p); + git_patch_free(p); /* diff from blob a to content of a */ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, a, NULL, a_content, strlen(a_content), NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p)); - git_diff_patch_free(p); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); /* diff from NULL blob to content of a */ - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, NULL, NULL, a_content, strlen(a_content), NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0)); - git_diff_patch_free(p); + cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); /* diff from blob a to NULL buffer */ - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, a, NULL, NULL, 0, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0)); - git_diff_patch_free(p); + cl_assert_equal_i(GIT_DELTA_DELETED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); /* diff with reverse */ opts.flags ^= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, a, NULL, NULL, 0, NULL, &opts)); cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p)); - cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0)); - git_diff_patch_free(p); + cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); git_blob_free(a); } @@ -853,7 +852,7 @@ void test_diff_blob__using_path_and_attributes(void) "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; size_t bin_len = 33; const char *changed; - git_diff_patch *p; + git_patch *p; char *pout; /* set up custom diff drivers and 'diff' attribute mappings for them */ @@ -950,9 +949,9 @@ void test_diff_blob__using_path_and_attributes(void) cl_assert_equal_i(3, expected.line_adds); cl_assert_equal_i(0, expected.line_dels); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.normal b/zzz.normal\n" "index 45141a7..75b0dbb 100644\n" @@ -963,21 +962,21 @@ void test_diff_blob__using_path_and_attributes(void) "+And more\n" "+Go here\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, nonbin, "zzz.binary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.binary b/zzz.binary\n" "index 45141a7..75b0dbb 100644\n" "Binary files a/zzz.binary and b/zzz.binary differ\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, nonbin, "zzz.alphary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.alphary b/zzz.alphary\n" "index 45141a7..75b0dbb 100644\n" @@ -988,11 +987,11 @@ void test_diff_blob__using_path_and_attributes(void) "+And more\n" "+Go here\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, nonbin, "zzz.numary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.numary b/zzz.numary\n" "index 45141a7..75b0dbb 100644\n" @@ -1003,7 +1002,7 @@ void test_diff_blob__using_path_and_attributes(void) "+And more\n" "+Go here\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); /* "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n" * 33 bytes @@ -1011,19 +1010,19 @@ void test_diff_blob__using_path_and_attributes(void) changed = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\nreplace a line\n"; - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, bin, "zzz.normal", changed, 37, NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.normal b/zzz.normal\n" "index b435cd5..1604519 100644\n" "Binary files a/zzz.normal and b/zzz.normal differ\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, bin, "zzz.textary", changed, 37, NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.textary b/zzz.textary\n" "index b435cd5..1604519 100644\n" @@ -1033,11 +1032,11 @@ void test_diff_blob__using_path_and_attributes(void) "-0123456789\n" "+replace a line\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, bin, "zzz.textalphary", changed, 37, NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.textalphary b/zzz.textalphary\n" "index b435cd5..1604519 100644\n" @@ -1047,11 +1046,11 @@ void test_diff_blob__using_path_and_attributes(void) "-0123456789\n" "+replace a line\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); - cl_git_pass(git_diff_patch_from_blob_and_buffer( + cl_git_pass(git_patch_from_blob_and_buffer( &p, bin, "zzz.textnumary", changed, 37, NULL, &opts)); - cl_git_pass(git_diff_patch_to_str(&pout, p)); + cl_git_pass(git_patch_to_str(&pout, p)); cl_assert_equal_s( "diff --git a/zzz.textnumary b/zzz.textnumary\n" "index b435cd5..1604519 100644\n" @@ -1061,7 +1060,7 @@ void test_diff_blob__using_path_and_attributes(void) "-0123456789\n" "+replace a line\n", pout); git__free(pout); - git_diff_patch_free(p); + git_patch_free(p); git_blob_free(nonbin); git_blob_free(bin); diff --git a/tests-clar/diff/diff_helpers.c b/tests/diff/diff_helpers.c similarity index 50% rename from tests-clar/diff/diff_helpers.c rename to tests/diff/diff_helpers.c index 4e23792a6..33bb561f6 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests/diff/diff_helpers.c @@ -21,6 +21,35 @@ git_tree *resolve_commit_oid_to_tree( return tree; } +static char diff_pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (GIT_PERMS_IS_EXEC(mode)) + return '*'; + else + return ' '; +} + +static void fprintf_delta(FILE *fp, const git_diff_delta *delta, float progress) +{ + char code = git_diff_status_char(delta->status); + char old_suffix = diff_pick_suffix(delta->old_file.mode); + char new_suffix = diff_pick_suffix(delta->new_file.mode); + + fprintf(fp, "%c\t%s", code, delta->old_file.path); + + if ((delta->old_file.path != delta->new_file.path && + strcmp(delta->old_file.path, delta->new_file.path) != 0) || + (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0)) + fprintf(fp, "%c %s%c", old_suffix, delta->new_file.path, new_suffix); + else if (old_suffix != ' ') + fprintf(fp, "%c", old_suffix); + + fprintf(fp, "\t[%.2f]\n", progress); +} + int diff_file_cb( const git_diff_delta *delta, float progress, @@ -29,9 +58,7 @@ int diff_file_cb( diff_expects *e = payload; if (e->debug) - fprintf(stderr, "%c %s (%.3f)\n", - git_diff_status_char(delta->status), - delta->old_file.path, progress); + fprintf_delta(stderr, delta, progress); if (e->names) cl_assert_equal_s(e->names[e->files], delta->old_file.path); @@ -55,47 +82,50 @@ int diff_print_file_cb( float progress, void *payload) { - fprintf(stderr, "%c %s\n", - git_diff_status_char(delta->status), delta->old_file.path); + if (!payload) { + fprintf_delta(stderr, delta, progress); + return 0; + } + + if (!((diff_expects *)payload)->debug) + fprintf_delta(stderr, delta, progress); + return diff_file_cb(delta, progress, payload); } int diff_hunk_cb( const git_diff_delta *delta, - const git_diff_range *range, - const char *header, - size_t header_len, + const git_diff_hunk *hunk, void *payload) { diff_expects *e = payload; + const char *scan = hunk->header, *scan_end = scan + hunk->header_len; GIT_UNUSED(delta); - GIT_UNUSED(header); - GIT_UNUSED(header_len); + + /* confirm no NUL bytes in header text */ + while (scan < scan_end) + cl_assert('\0' != *scan++); e->hunks++; - e->hunk_old_lines += range->old_lines; - e->hunk_new_lines += range->new_lines; + e->hunk_old_lines += hunk->old_lines; + e->hunk_new_lines += hunk->new_lines; return 0; } int diff_line_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *payload) { diff_expects *e = payload; GIT_UNUSED(delta); - GIT_UNUSED(range); - GIT_UNUSED(content); - GIT_UNUSED(content_len); + GIT_UNUSED(hunk); e->lines++; - switch (line_origin) { + switch (line->origin) { case GIT_DIFF_LINE_CONTEXT: case GIT_DIFF_LINE_CONTEXT_EOFNL: /* techically not a line */ e->line_ctxt++; @@ -115,25 +145,25 @@ int diff_line_cb( } int diff_foreach_via_iterator( - git_diff_list *diff, + git_diff *diff, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *data) { size_t d, num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; + git_patch *patch; const git_diff_delta *delta; size_t h, num_h; - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - cl_assert(delta); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); /* call file_cb for this file */ if (file_cb != NULL && file_cb(delta, (float)d / num_d, data) != 0) { - git_diff_patch_free(patch); + git_patch_free(patch); goto abort; } @@ -145,44 +175,37 @@ int diff_foreach_via_iterator( } if (!hunk_cb && !line_cb) { - git_diff_patch_free(patch); + git_patch_free(patch); continue; } - num_h = git_diff_patch_num_hunks(patch); + num_h = git_patch_num_hunks(patch); for (h = 0; h < num_h; h++) { - const git_diff_range *range; - const char *hdr; - size_t hdr_len, l, num_l; + const git_diff_hunk *hunk; + size_t l, num_l; - cl_git_pass(git_diff_patch_get_hunk( - &range, &hdr, &hdr_len, &num_l, patch, h)); + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); - if (hunk_cb && hunk_cb(delta, range, hdr, hdr_len, data) != 0) { - git_diff_patch_free(patch); + if (hunk_cb && hunk_cb(delta, hunk, data) != 0) { + git_patch_free(patch); goto abort; } for (l = 0; l < num_l; ++l) { - char origin; - const char *line; - size_t line_len; - int old_lineno, new_lineno; + const git_diff_line *line; - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &line, &line_len, &old_lineno, &new_lineno, - patch, h, l)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); if (line_cb && - line_cb(delta, range, origin, line, line_len, data) != 0) { - git_diff_patch_free(patch); + line_cb(delta, hunk, line, data) != 0) { + git_patch_free(patch); goto abort; } } } - git_diff_patch_free(patch); + git_patch_free(patch); } return 0; @@ -194,27 +217,30 @@ abort: static int diff_print_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, /**< GIT_DIFF_LINE_... value from above */ - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *payload) { - GIT_UNUSED(payload); - GIT_UNUSED(delta); - GIT_UNUSED(range); - GIT_UNUSED(line_origin); - GIT_UNUSED(content_len); - fputs(content, (FILE *)payload); + FILE *fp = payload; + + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) + fputc(line->origin, fp); + fwrite(line->content, 1, line->content_len, fp); return 0; } -void diff_print(FILE *fp, git_diff_list *diff) +void diff_print(FILE *fp, git_diff *diff) { - cl_git_pass(git_diff_print_patch(diff, diff_print_cb, fp ? fp : stderr)); + cl_git_pass(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH, diff_print_cb, fp ? fp : stderr)); } -void diff_print_raw(FILE *fp, git_diff_list *diff) +void diff_print_raw(FILE *fp, git_diff *diff) { - cl_git_pass(git_diff_print_raw(diff, diff_print_cb, fp ? fp : stderr)); + cl_git_pass(git_diff_print( + diff, GIT_DIFF_FORMAT_RAW, diff_print_cb, fp ? fp : stderr)); } diff --git a/tests-clar/diff/diff_helpers.h b/tests/diff/diff_helpers.h similarity index 75% rename from tests-clar/diff/diff_helpers.h rename to tests/diff/diff_helpers.h index bb76d0076..bf21f4b1f 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests/diff/diff_helpers.h @@ -44,25 +44,21 @@ extern int diff_print_file_cb( extern int diff_hunk_cb( const git_diff_delta *delta, - const git_diff_range *range, - const char *header, - size_t header_len, + const git_diff_hunk *hunk, void *cb_data); extern int diff_line_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *cb_data); extern int diff_foreach_via_iterator( - git_diff_list *diff, + git_diff *diff, git_diff_file_cb file_cb, git_diff_hunk_cb hunk_cb, - git_diff_data_cb line_cb, + git_diff_line_cb line_cb, void *data); -extern void diff_print(FILE *fp, git_diff_list *diff); -extern void diff_print_raw(FILE *fp, git_diff_list *diff); +extern void diff_print(FILE *fp, git_diff *diff); +extern void diff_print_raw(FILE *fp, git_diff *diff); diff --git a/tests-clar/diff/diffiter.c b/tests/diff/diffiter.c similarity index 76% rename from tests-clar/diff/diffiter.c rename to tests/diff/diffiter.c index 932d720f2..f886e1baa 100644 --- a/tests-clar/diff/diffiter.c +++ b/tests/diff/diffiter.c @@ -13,47 +13,48 @@ void test_diff_diffiter__cleanup(void) void test_diff_diffiter__create(void) { git_repository *repo = cl_git_sandbox_init("attr"); - git_diff_list *diff; + git_diff *diff; size_t d, num_d; cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta; - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d)); + const git_diff_delta *delta = git_diff_get_delta(diff, d); + cl_assert(delta != NULL); } - git_diff_list_free(diff); + cl_assert(!git_diff_get_delta(diff, num_d)); + + git_diff_free(diff); } -void test_diff_diffiter__iterate_files(void) +void test_diff_diffiter__iterate_files_1(void) { git_repository *repo = cl_git_sandbox_init("attr"); - git_diff_list *diff; + git_diff *diff; size_t d, num_d; - int count = 0; + diff_expects exp = { 0 }; cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); num_d = git_diff_num_deltas(diff); - cl_assert_equal_i(6, (int)num_d); for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta; - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d)); + const git_diff_delta *delta = git_diff_get_delta(diff, d); cl_assert(delta != NULL); - count++; - } - cl_assert_equal_i(6, count); - git_diff_list_free(diff); + diff_file_cb(delta, (float)d / (float)num_d, &exp); + } + cl_assert_equal_sz(6, exp.files); + + git_diff_free(diff); } void test_diff_diffiter__iterate_files_2(void) { git_repository *repo = cl_git_sandbox_init("status"); - git_diff_list *diff; + git_diff *diff; size_t d, num_d; int count = 0; @@ -63,21 +64,20 @@ void test_diff_diffiter__iterate_files_2(void) cl_assert_equal_i(8, (int)num_d); for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta; - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d)); + const git_diff_delta *delta = git_diff_get_delta(diff, d); cl_assert(delta != NULL); count++; } cl_assert_equal_i(8, count); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_diffiter__iterate_files_and_hunks(void) { git_repository *repo = cl_git_sandbox_init("status"); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; size_t d, num_d; int file_count = 0, hunk_count = 0; @@ -90,47 +90,39 @@ void test_diff_diffiter__iterate_files_and_hunks(void) num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; - const git_diff_delta *delta; + git_patch *patch; size_t h, num_h; - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - - cl_assert(delta); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); cl_assert(patch); file_count++; - num_h = git_diff_patch_num_hunks(patch); + num_h = git_patch_num_hunks(patch); for (h = 0; h < num_h; h++) { - const git_diff_range *range; - const char *header; - size_t header_len, num_l; + const git_diff_hunk *hunk; - cl_git_pass(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, h)); - - cl_assert(range); - cl_assert(header); + cl_git_pass(git_patch_get_hunk(&hunk, NULL, patch, h)); + cl_assert(hunk); hunk_count++; } - git_diff_patch_free(patch); + git_patch_free(patch); } cl_assert_equal_i(13, file_count); cl_assert_equal_i(8, hunk_count); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_diffiter__max_size_threshold(void) { git_repository *repo = cl_git_sandbox_init("status"); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; int file_count = 0, binary_count = 0, hunk_count = 0; size_t d, num_d; @@ -142,27 +134,28 @@ void test_diff_diffiter__max_size_threshold(void) num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; + git_patch *patch; const git_diff_delta *delta; - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - cl_assert(delta); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); cl_assert(patch); + delta = git_patch_get_delta(patch); + cl_assert(delta); file_count++; - hunk_count += (int)git_diff_patch_num_hunks(patch); + hunk_count += (int)git_patch_num_hunks(patch); assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - git_diff_patch_free(patch); + git_patch_free(patch); } cl_assert_equal_i(13, file_count); cl_assert_equal_i(0, binary_count); cl_assert_equal_i(8, hunk_count); - git_diff_list_free(diff); + git_diff_free(diff); /* try again with low file size threshold */ @@ -177,18 +170,19 @@ void test_diff_diffiter__max_size_threshold(void) num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; + git_patch *patch; const git_diff_delta *delta; - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + delta = git_patch_get_delta(patch); file_count++; - hunk_count += (int)git_diff_patch_num_hunks(patch); + hunk_count += (int)git_patch_num_hunks(patch); assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - git_diff_patch_free(patch); + git_patch_free(patch); } cl_assert_equal_i(13, file_count); @@ -200,7 +194,7 @@ void test_diff_diffiter__max_size_threshold(void) cl_assert_equal_i(3, binary_count); cl_assert_equal_i(5, hunk_count); - git_diff_list_free(diff); + git_diff_free(diff); } @@ -208,7 +202,7 @@ void test_diff_diffiter__iterate_all(void) { git_repository *repo = cl_git_sandbox_init("status"); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp = {0}; size_t d, num_d; @@ -220,57 +214,51 @@ void test_diff_diffiter__iterate_all(void) num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; - const git_diff_delta *delta; + git_patch *patch; size_t h, num_h; - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - cl_assert(patch && delta); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); exp.files++; - num_h = git_diff_patch_num_hunks(patch); + num_h = git_patch_num_hunks(patch); for (h = 0; h < num_h; h++) { - const git_diff_range *range; - const char *header; - size_t header_len, l, num_l; + const git_diff_hunk *range; + size_t l, num_l; - cl_git_pass(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, h)); - cl_assert(range && header); + cl_git_pass(git_patch_get_hunk(&range, &num_l, patch, h)); + cl_assert(range); exp.hunks++; for (l = 0; l < num_l; ++l) { - char origin; - const char *content; - size_t content_len; + const git_diff_line *line; - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &content, &content_len, NULL, NULL, patch, h, l)); - cl_assert(content); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); + cl_assert(line && line->content); exp.lines++; } } - git_diff_patch_free(patch); + git_patch_free(patch); } cl_assert_equal_i(13, exp.files); cl_assert_equal_i(8, exp.hunks); cl_assert_equal_i(14, exp.lines); - git_diff_list_free(diff); + git_diff_free(diff); } -static void iterate_over_patch(git_diff_patch *patch, diff_expects *exp) +static void iterate_over_patch(git_patch *patch, diff_expects *exp) { - size_t h, num_h = git_diff_patch_num_hunks(patch), num_l; + size_t h, num_h = git_patch_num_hunks(patch), num_l; exp->files++; exp->hunks += (int)num_h; /* let's iterate in reverse, just because we can! */ for (h = 1, num_l = 0; h <= num_h; ++h) - num_l += git_diff_patch_num_lines_in_hunk(patch, num_h - h); + num_l += git_patch_num_lines_in_hunk(patch, num_h - h); exp->lines += (int)num_l; } @@ -281,9 +269,9 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void) { git_repository *repo = cl_git_sandbox_init("status"); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp = {0}; - git_diff_patch *patches[PATCH_CACHE]; + git_patch *patches[PATCH_CACHE]; size_t p, d, num_d; memset(patches, 0, sizeof(patches)); @@ -308,32 +296,32 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void) for (d = 0; d < num_d; ++d) { /* take old patch */ - git_diff_patch *patch = patches[p]; + git_patch *patch = patches[p]; patches[p] = NULL; /* cache new patch */ - cl_git_pass(git_diff_get_patch(&patches[p], NULL, diff, d)); + cl_git_pass(git_patch_from_diff(&patches[p], diff, d)); cl_assert(patches[p] != NULL); /* process old patch if non-NULL */ if (patch != NULL) { iterate_over_patch(patch, &exp); - git_diff_patch_free(patch); + git_patch_free(patch); } p = rand() % PATCH_CACHE; } /* free diff list now - refcounts should keep things safe */ - git_diff_list_free(diff); + git_diff_free(diff); /* process remaining unprocessed patches */ for (p = 0; p < PATCH_CACHE; p++) { - git_diff_patch *patch = patches[p]; + git_patch *patch = patches[p]; if (patch != NULL) { iterate_over_patch(patch, &exp); - git_diff_patch_free(patch); + git_patch_free(patch); } } @@ -416,7 +404,7 @@ static const char *expected_patch_text[8] = { void test_diff_diffiter__iterate_and_generate_patch_text(void) { git_repository *repo = cl_git_sandbox_init("status"); - git_diff_list *diff; + git_diff *diff; size_t d, num_d; cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); @@ -425,28 +413,28 @@ void test_diff_diffiter__iterate_and_generate_patch_text(void) cl_assert_equal_i(8, (int)num_d); for (d = 0; d < num_d; ++d) { - git_diff_patch *patch; + git_patch *patch; char *text; - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d)); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); cl_assert(patch != NULL); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected_patch_text[d], text); git__free(text); - git_diff_patch_free(patch); + git_patch_free(patch); } - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_diffiter__checks_options_version(void) { git_repository *repo = cl_git_sandbox_init("status"); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; const git_error *err; opts.version = 0; diff --git a/tests-clar/diff/drivers.c b/tests/diff/drivers.c similarity index 50% rename from tests-clar/diff/drivers.c rename to tests/diff/drivers.c index 06ab2ff14..fbd1dff81 100644 --- a/tests-clar/diff/drivers.c +++ b/tests/diff/drivers.c @@ -20,8 +20,8 @@ void test_diff_drivers__patterns(void) git_config *cfg; const char *one_sha = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; git_tree *one; - git_diff_list *diff; - git_diff_patch *patch; + git_diff *diff; + git_patch *patch; char *text; const char *expected0 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Comes through the blood of the vanguards who\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n"; const char *expected1 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\nBinary files a/untimely.txt and b/untimely.txt differ\n"; @@ -35,7 +35,7 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(0, (int)git_diff_num_deltas(diff)); - git_diff_list_free(diff); + git_diff_free(diff); /* default diff */ @@ -44,13 +44,13 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected0, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); /* attribute diff set to false */ @@ -59,13 +59,13 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected1, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); /* attribute diff set to unconfigured value (should use default) */ @@ -74,13 +74,13 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected0, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); /* let's define that driver */ @@ -91,13 +91,13 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected1, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); /* let's use a real driver with some regular expressions */ @@ -112,14 +112,52 @@ void test_diff_drivers__patterns(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected2, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); git_tree_free(one); } +void test_diff_drivers__long_lines(void) +{ + const char *base = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi ligula. Ut viverra enim sed lobortis suscipit.\nPhasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissim risus. Suspendisse at nisi quis turpis fringilla rutrum id sit amet nulla.\nNam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\nMauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\nAliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n"; + git_index *idx; + git_diff *diff; + git_patch *patch; + char *actual; + const char *expected = "diff --git a/longlines.txt b/longlines.txt\nindex c1ce6ef..0134431 100644\n--- a/longlines.txt\n+++ b/longlines.txt\n@@ -3,3 +3,5 @@ Phasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissi\n Nam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\n Mauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\n Aliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n+newline\n+newline\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/longlines.txt", base); + cl_git_pass(git_repository_index(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "longlines.txt")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + cl_git_append2file("empty_standard_repo/longlines.txt", "newline\nnewline\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + cl_assert_equal_sz(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&actual, patch)); + + /* if chmod not supported, overwrite mode bits since anything is possible */ + if (!cl_is_chmod_supported()) { + size_t actual_len = strlen(actual); + if (actual_len > 72 && memcmp(&actual[66], "100644", 6) != 0) + memcpy(&actual[66], "100644", 6); + } + + cl_assert_equal_s(expected, actual); + + free(actual); + git_patch_free(patch); + git_diff_free(diff); +} + diff --git a/tests-clar/diff/index.c b/tests/diff/index.c similarity index 96% rename from tests-clar/diff/index.c rename to tests/diff/index.c index e1c617dae..8f4567137 100644 --- a/tests-clar/diff/index.c +++ b/tests/diff/index.c @@ -21,7 +21,7 @@ void test_diff_index__0(void) git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; cl_assert(a); @@ -56,7 +56,7 @@ void test_diff_index__0(void) cl_assert_equal_i(6, exp.line_adds); cl_assert_equal_i(2, exp.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; memset(&exp, 0, sizeof(exp)); @@ -84,7 +84,7 @@ void test_diff_index__0(void) cl_assert_equal_i(11, exp.line_adds); cl_assert_equal_i(2, exp.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; git_tree_free(a); @@ -114,7 +114,7 @@ void test_diff_index__1(void) git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; cl_assert(a); @@ -134,7 +134,7 @@ void test_diff_index__1(void) cl_assert_equal_i(2, exp.files); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; git_tree_free(a); @@ -146,7 +146,7 @@ void test_diff_index__checks_options_version(void) const char *a_commit = "26a125ee1bf"; git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; const git_error *err; opts.version = 0; diff --git a/tests-clar/diff/iterator.c b/tests/diff/iterator.c similarity index 100% rename from tests-clar/diff/iterator.c rename to tests/diff/iterator.c diff --git a/tests-clar/diff/notify.c b/tests/diff/notify.c similarity index 95% rename from tests-clar/diff/notify.c rename to tests/diff/notify.c index 433b4a9c1..cc33cb71c 100644 --- a/tests-clar/diff/notify.c +++ b/tests/diff/notify.c @@ -13,7 +13,7 @@ void test_diff_notify__cleanup(void) } static int assert_called_notifications( - const git_diff_list *diff_so_far, + const git_diff *diff_so_far, const git_diff_delta *delta_to_add, const char *matched_pathspec, void *payload) @@ -45,7 +45,7 @@ static void test_notify( int expected_diffed_files_count) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; g_repo = cl_git_sandbox_init("status"); @@ -63,7 +63,7 @@ static void test_notify( cl_assert_equal_i(expected_diffed_files_count, exp.files); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_notify__notify_single_pathspec(void) @@ -155,7 +155,7 @@ void test_diff_notify__notify_catchall(void) } static int abort_diff( - const git_diff_list *diff_so_far, + const git_diff *diff_so_far, const git_diff_delta *delta_to_add, const char *matched_pathspec, void *payload) @@ -171,7 +171,7 @@ static int abort_diff( void test_diff_notify__notify_cb_can_abort_diff(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; char *pathspec = NULL; g_repo = cl_git_sandbox_init("status"); @@ -189,7 +189,7 @@ void test_diff_notify__notify_cb_can_abort_diff(void) } static int filter_all( - const git_diff_list *diff_so_far, + const git_diff *diff_so_far, const git_diff_delta *delta_to_add, const char *matched_pathspec, void *payload) @@ -205,7 +205,7 @@ static int filter_all( void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; char *pathspec = NULL; diff_expects exp; @@ -224,5 +224,5 @@ void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void) cl_assert_equal_i(0, exp.files); - git_diff_list_free(diff); + git_diff_free(diff); } diff --git a/tests-clar/diff/patch.c b/tests/diff/patch.c similarity index 58% rename from tests-clar/diff/patch.c rename to tests/diff/patch.c index 3f14a0de7..bd1598b21 100644 --- a/tests-clar/diff/patch.c +++ b/tests/diff/patch.c @@ -26,40 +26,37 @@ void test_diff_patch__cleanup(void) static int check_removal_cb( const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *formatted_output, - size_t output_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *payload) { GIT_UNUSED(payload); - GIT_UNUSED(output_len); - switch (line_origin) { + switch (line->origin) { case GIT_DIFF_LINE_FILE_HDR: - cl_assert_equal_s(EXPECTED_HEADER, formatted_output); - cl_assert(range == NULL); + cl_assert_equal_s(EXPECTED_HEADER, line->content); + cl_assert(hunk == NULL); goto check_delta; case GIT_DIFF_LINE_HUNK_HDR: - cl_assert_equal_s(EXPECTED_HUNK, formatted_output); + cl_assert_equal_s(EXPECTED_HUNK, line->content); /* Fall through */ case GIT_DIFF_LINE_CONTEXT: case GIT_DIFF_LINE_DELETION: - goto check_range; + goto check_hunk; default: /* unexpected code path */ return -1; } -check_range: - cl_assert(range != NULL); - cl_assert_equal_i(1, range->old_start); - cl_assert_equal_i(2, range->old_lines); - cl_assert_equal_i(0, range->new_start); - cl_assert_equal_i(0, range->new_lines); +check_hunk: + cl_assert(hunk != NULL); + cl_assert_equal_i(1, hunk->old_start); + cl_assert_equal_i(2, hunk->old_lines); + cl_assert_equal_i(0, hunk->new_start); + cl_assert_equal_i(0, hunk->new_lines); check_delta: cl_assert_equal_s("subdir.txt", delta->old_file.path); @@ -86,7 +83,7 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void) const char *one_sha = "26a125e"; const char *another_sha = "735b6a2"; git_tree *one, *another; - git_diff_list *diff; + git_diff *diff; g_repo = cl_git_sandbox_init("status"); @@ -95,9 +92,10 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void) cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); - cl_git_pass(git_diff_print_patch(diff, check_removal_cb, NULL)); + cl_git_pass(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL)); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(another); git_tree_free(one); @@ -108,8 +106,8 @@ void test_diff_patch__to_string(void) const char *one_sha = "26a125e"; const char *another_sha = "735b6a2"; git_tree *one, *another; - git_diff_list *diff; - git_diff_patch *patch; + git_diff *diff; + git_patch *patch; char *text; const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n"; @@ -122,15 +120,20 @@ void test_diff_patch__to_string(void) cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected, text); + cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0)); + cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0)); + cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0)); + cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1)); + git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); git_tree_free(another); git_tree_free(one); } @@ -140,8 +143,8 @@ void test_diff_patch__config_options(void) const char *one_sha = "26a125e"; /* current HEAD */ git_tree *one; git_config *cfg; - git_diff_list *diff; - git_diff_patch *patch; + git_diff *diff; + git_patch *patch; char *text; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; char *onefile = "staged_changes_modified_file"; @@ -162,24 +165,24 @@ void test_diff_patch__config_options(void) cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected1, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected2, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true")); @@ -187,13 +190,13 @@ void test_diff_patch__config_options(void) cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected3, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12)); @@ -201,13 +204,13 @@ void test_diff_patch__config_options(void) cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected4, text); git__free(text); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); git_tree_free(one); git_config_free(cfg); @@ -218,14 +221,12 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) git_config *cfg; git_tree *head; git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; - git_diff_patch *patch; + git_diff *diff; + git_patch *patch; const git_diff_delta *delta; - const git_diff_range *range; - const char *hdr, *text; - size_t hdrlen, hunklen, textlen; - char origin; - int oldno, newno; + const git_diff_hunk *hunk; + const git_diff_line *line; + size_t hunklen; git_buf old_content = GIT_BUF_INIT, actual = GIT_BUF_INIT; const char *new_content = "The Song of Seven Cities\n------------------------\n\nI WAS Lord of Cities very sumptuously builded.\nSeven roaring Cities paid me tribute from afar.\nIvory their outposts were--the guardrooms of them gilded,\nAnd garrisoned with Amazons invincible in war.\n\nThis is some new text;\nNot as good as the old text;\nBut here it is.\n\nSo they warred and trafficked only yesterday, my Cities.\nTo-day there is no mark or mound of where my Cities stood.\nFor the River rose at midnight and it washed away my Cities.\nThey are evened with Atlantis and the towns before the Flood.\n\nRain on rain-gorged channels raised the water-levels round them,\nFreshet backed on freshet swelled and swept their world from sight,\nTill the emboldened floods linked arms and, flashing forward, drowned them--\nDrowned my Seven Cities and their peoples in one night!\n\nLow among the alders lie their derelict foundations,\nThe beams wherein they trusted and the plinths whereon they built--\nMy rulers and their treasure and their unborn populations,\nDead, destroyed, aborted, and defiled with mud and silt!\n\nAnother replacement;\nBreaking up the poem;\nGenerating some hunks.\n\nTo the sound of trumpets shall their seed restore my Cities\nWealthy and well-weaponed, that once more may I behold\nAll the world go softly when it walks before my Cities,\nAnd the horses and the chariots fleeing from them as of old!\n\n -- Rudyard Kipling\n"; @@ -233,6 +234,9 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) cl_git_pass(git_config_new(&cfg)); git_repository_set_config(g_repo, cfg); + git_config_free(cfg); + + git_repository_reinit_filesystem(g_repo, false); cl_git_pass( git_futils_readbuffer(&old_content, "renames/songof7cities.txt")); @@ -245,89 +249,89 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, 0)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_assert_equal_i(2, (int)git_diff_patch_num_hunks(patch)); + cl_assert_equal_i(2, (int)git_patch_num_hunks(patch)); /* check hunk 0 */ cl_git_pass( - git_diff_patch_get_hunk(&range, &hdr, &hdrlen, &hunklen, patch, 0)); + git_patch_get_hunk(&hunk, &hunklen, patch, 0)); cl_assert_equal_i(18, (int)hunklen); - cl_assert_equal_i(6, (int)range->old_start); - cl_assert_equal_i(15, (int)range->old_lines); - cl_assert_equal_i(6, (int)range->new_start); - cl_assert_equal_i(9, (int)range->new_lines); + cl_assert_equal_i(6, (int)hunk->old_start); + cl_assert_equal_i(15, (int)hunk->old_lines); + cl_assert_equal_i(6, (int)hunk->new_start); + cl_assert_equal_i(9, (int)hunk->new_lines); - cl_assert_equal_i(18, (int)git_diff_patch_num_lines_in_hunk(patch, 0)); + cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0)); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 0)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr); - cl_assert_equal_i(6, oldno); - cl_assert_equal_i(6, newno); + cl_assert_equal_i(6, line->old_lineno); + cl_assert_equal_i(6, line->new_lineno); + cl_assert_equal_i(-1, line->content_offset); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr); - cl_assert_equal_i(9, oldno); - cl_assert_equal_i(-1, newno); + cl_assert_equal_i(9, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); + cl_assert_equal_i(252, line->content_offset); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 12)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("This is some new text;\n", actual.ptr); - cl_assert_equal_i(-1, oldno); - cl_assert_equal_i(9, newno); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(9, line->new_lineno); + cl_assert_equal_i(252, line->content_offset); /* check hunk 1 */ - cl_git_pass( - git_diff_patch_get_hunk(&range, &hdr, &hdrlen, &hunklen, patch, 1)); + cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1)); cl_assert_equal_i(18, (int)hunklen); - cl_assert_equal_i(31, (int)range->old_start); - cl_assert_equal_i(15, (int)range->old_lines); - cl_assert_equal_i(25, (int)range->new_start); - cl_assert_equal_i(9, (int)range->new_lines); + cl_assert_equal_i(31, (int)hunk->old_start); + cl_assert_equal_i(15, (int)hunk->old_lines); + cl_assert_equal_i(25, (int)hunk->new_start); + cl_assert_equal_i(9, (int)hunk->new_lines); - cl_assert_equal_i(18, (int)git_diff_patch_num_lines_in_hunk(patch, 1)); + cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1)); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 1, 0)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr); - cl_assert_equal_i(31, oldno); - cl_assert_equal_i(25, newno); + cl_assert_equal_i(31, line->old_lineno); + cl_assert_equal_i(25, line->new_lineno); + cl_assert_equal_i(-1, line->content_offset); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 1, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr); - cl_assert_equal_i(34, oldno); - cl_assert_equal_i(-1, newno); + cl_assert_equal_i(34, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); + cl_assert_equal_i(1468, line->content_offset); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 1, 12)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("Another replacement;\n", actual.ptr); - cl_assert_equal_i(-1, oldno); - cl_assert_equal_i(28, newno); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(28, line->new_lineno); + cl_assert_equal_i(1066, line->content_offset); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); /* Let's check line numbers when there is no newline */ @@ -338,81 +342,75 @@ void test_diff_patch__hunks_have_correct_line_numbers(void) cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, 0)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(patch)); + cl_assert_equal_i(1, (int)git_patch_num_hunks(patch)); /* check hunk 0 */ - cl_git_pass( - git_diff_patch_get_hunk(&range, &hdr, &hdrlen, &hunklen, patch, 0)); + cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0)); cl_assert_equal_i(6, (int)hunklen); - cl_assert_equal_i(46, (int)range->old_start); - cl_assert_equal_i(4, (int)range->old_lines); - cl_assert_equal_i(46, (int)range->new_start); - cl_assert_equal_i(4, (int)range->new_lines); + cl_assert_equal_i(46, (int)hunk->old_start); + cl_assert_equal_i(4, (int)hunk->old_lines); + cl_assert_equal_i(46, (int)hunk->new_start); + cl_assert_equal_i(4, (int)hunk->new_lines); - cl_assert_equal_i(6, (int)git_diff_patch_num_lines_in_hunk(patch, 0)); + cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0)); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 1)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr); - cl_assert_equal_i(47, oldno); - cl_assert_equal_i(47, newno); + cl_assert_equal_i(47, line->old_lineno); + cl_assert_equal_i(47, line->new_lineno); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 2)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("\n", actual.ptr); - cl_assert_equal_i(48, oldno); - cl_assert_equal_i(48, newno); + cl_assert_equal_i(48, line->old_lineno); + cl_assert_equal_i(48, line->new_lineno); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s(" -- Rudyard Kipling\n", actual.ptr); - cl_assert_equal_i(49, oldno); - cl_assert_equal_i(-1, newno); + cl_assert_equal_i(49, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 4)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s(" -- Rudyard Kipling", actual.ptr); - cl_assert_equal_i(-1, oldno); - cl_assert_equal_i(49, newno); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(49, line->new_lineno); - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &text, &textlen, &oldno, &newno, patch, 0, 5)); - cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)origin); - cl_git_pass(git_buf_set(&actual, text, textlen)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5)); + cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin); + cl_git_pass(git_buf_set(&actual, line->content, line->content_len)); cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr); - cl_assert_equal_i(-1, oldno); - cl_assert_equal_i(49, newno); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(49, line->new_lineno); - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); git_buf_free(&actual); git_buf_free(&old_content); git_tree_free(head); - git_config_free(cfg); } static void check_single_patch_stats( git_repository *repo, size_t hunks, - size_t adds, size_t dels, size_t ctxt, + size_t adds, size_t dels, size_t ctxt, size_t *sizes, const char *expected) { - git_diff_list *diff; - git_diff_patch *patch; + git_diff *diff; + git_patch *patch; const git_diff_delta *delta; size_t actual_ctxt, actual_adds, actual_dels; @@ -420,12 +418,13 @@ static void check_single_patch_stats( cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, 0)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_assert_equal_i((int)hunks, (int)git_diff_patch_num_hunks(patch)); + cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch)); - cl_git_pass( git_diff_patch_line_stats( + cl_git_pass( git_patch_line_stats( &actual_ctxt, &actual_adds, &actual_dels, patch) ); cl_assert_equal_sz(ctxt, actual_ctxt); @@ -434,45 +433,60 @@ static void check_single_patch_stats( if (expected != NULL) { char *text; - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected, text); git__free(text); + + cl_assert_equal_sz( + strlen(expected), git_patch_size(patch, 1, 1, 1)); + } + + if (sizes) { + if (sizes[0]) + cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0)); + if (sizes[1]) + cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0)); + if (sizes[2]) + cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0)); } /* walk lines in hunk with basic sanity checks */ for (; hunks > 0; --hunks) { size_t i, max_i; - int lastoldno = -1, oldno, lastnewno = -1, newno; - char origin; + const git_diff_line *line; + int last_new_lineno = -1, last_old_lineno = -1; - max_i = git_diff_patch_num_lines_in_hunk(patch, hunks - 1); + max_i = git_patch_num_lines_in_hunk(patch, hunks - 1); for (i = 0; i < max_i; ++i) { int expected = 1; - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, NULL, NULL, &oldno, &newno, patch, hunks - 1, i)); + cl_git_pass( + git_patch_get_line_in_hunk(&line, patch, hunks - 1, i)); - if (origin == GIT_DIFF_LINE_ADD_EOFNL || - origin == GIT_DIFF_LINE_DEL_EOFNL || - origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + if (line->origin == GIT_DIFF_LINE_ADD_EOFNL || + line->origin == GIT_DIFF_LINE_DEL_EOFNL || + line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) expected = 0; - if (oldno >= 0) { - if (lastoldno >= 0) - cl_assert_equal_i(expected, oldno - lastoldno); - lastoldno = oldno; + if (line->old_lineno >= 0) { + if (last_old_lineno >= 0) + cl_assert_equal_i( + expected, line->old_lineno - last_old_lineno); + last_old_lineno = line->old_lineno; } - if (newno >= 0) { - if (lastnewno >= 0) - cl_assert_equal_i(expected, newno - lastnewno); - lastnewno = newno; + + if (line->new_lineno >= 0) { + if (last_new_lineno >= 0) + cl_assert_equal_i( + expected, line->new_lineno - last_new_lineno); + last_new_lineno = line->new_lineno; } } } - git_diff_patch_free(patch); - git_diff_list_free(diff); + git_patch_free(patch); + git_diff_free(diff); } void test_diff_patch__line_counts_with_eofnl(void) @@ -481,11 +495,31 @@ void test_diff_patch__line_counts_with_eofnl(void) git_buf content = GIT_BUF_INIT; const char *end; git_index *index; + const char *expected = + /* below is pasted output of 'git diff' with fn context removed */ + "diff --git a/songof7cities.txt b/songof7cities.txt\n" + "index 378a7d9..3d0154e 100644\n" + "--- a/songof7cities.txt\n" + "+++ b/songof7cities.txt\n" + "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" + " \n" + " To the sound of trumpets shall their seed restore my Cities\n" + " Wealthy and well-weaponed, that once more may I behold\n" + "-All the world go softly when it walks before my Cities,\n" + "+#All the world go softly when it walks before my Cities,\n" + " And the horses and the chariots fleeing from them as of old!\n" + " \n" + " -- Rudyard Kipling\n" + "\\ No newline at end of file\n"; + size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 }; g_repo = cl_git_sandbox_init("renames"); cl_git_pass(git_config_new(&cfg)); git_repository_set_config(g_repo, cfg); + git_config_free(cfg); + + git_repository_reinit_filesystem(g_repo, false); cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt")); @@ -495,14 +529,14 @@ void test_diff_patch__line_counts_with_eofnl(void) git_buf_consume(&content, end); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL); + check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL); /* remove trailing whitespace */ git_buf_rtrim(&content); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL); + check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL); /* add trailing whitespace */ @@ -514,7 +548,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_pass(git_buf_putc(&content, '\n')); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL); + check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL); /* no trailing whitespace as context line */ @@ -537,23 +571,7 @@ void test_diff_patch__line_counts_with_eofnl(void) cl_git_rewritefile("renames/songof7cities.txt", content.ptr); check_single_patch_stats( - g_repo, 1, 1, 1, 6, - /* below is pasted output of 'git diff' with fn context removed */ - "diff --git a/songof7cities.txt b/songof7cities.txt\n" - "index 378a7d9..3d0154e 100644\n" - "--- a/songof7cities.txt\n" - "+++ b/songof7cities.txt\n" - "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" - " \n" - " To the sound of trumpets shall their seed restore my Cities\n" - " Wealthy and well-weaponed, that once more may I behold\n" - "-All the world go softly when it walks before my Cities,\n" - "+#All the world go softly when it walks before my Cities,\n" - " And the horses and the chariots fleeing from them as of old!\n" - " \n" - " -- Rudyard Kipling\n" - "\\ No newline at end of file\n"); + g_repo, 1, 1, 1, 6, expected_sizes, expected); git_buf_free(&content); - git_config_free(cfg); } diff --git a/tests/diff/pathspec.c b/tests/diff/pathspec.c new file mode 100644 index 000000000..5761d2d2b --- /dev/null +++ b/tests/diff/pathspec.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_pathspec__0(void) +{ + const char *a_commit = "26a125ee"; /* the current HEAD */ + const char *b_commit = "0017bd4a"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_strarray paths = { NULL, 1 }; + char *path; + git_pathspec *ps; + git_pathspec_match_list *matches; + + cl_assert(a); + cl_assert(b); + + path = "*_file"; + paths.strings = &path; + cl_git_pass(git_pathspec_new(&ps, &paths)); + + cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0)); + cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL); + git_pathspec_match_list_free(matches); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_ADDED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(3, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("subdir/current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_DELETED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(4, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("modified_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_MODIFIED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); + git_pathspec_free(ps); +} diff --git a/tests-clar/diff/rename.c b/tests/diff/rename.c similarity index 83% rename from tests-clar/diff/rename.c rename to tests/diff/rename.c index 2b1873bd5..42bb65aa8 100644 --- a/tests-clar/diff/rename.c +++ b/tests/diff/rename.c @@ -7,6 +7,8 @@ static git_repository *g_repo = NULL; void test_diff_rename__initialize(void) { g_repo = cl_git_sandbox_init("renames"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); } void test_diff_rename__cleanup(void) @@ -41,7 +43,7 @@ void test_diff_rename__match_oid(void) const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; git_tree *old_tree, *new_tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -86,7 +88,7 @@ void test_diff_rename__match_oid(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); cl_git_pass(git_diff_tree_to_tree( &diff, g_repo, old_tree, new_tree, &diffopts)); @@ -107,7 +109,7 @@ void test_diff_rename__match_oid(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(old_tree); git_tree_free(new_tree); @@ -118,7 +120,7 @@ void test_diff_rename__checks_options_version(void) const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; git_tree *old_tree, *new_tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; const git_error *err; @@ -140,7 +142,7 @@ void test_diff_rename__checks_options_version(void) err = giterr_last(); cl_assert_equal_i(GITERR_INVALID, err->klass); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(old_tree); git_tree_free(new_tree); } @@ -151,7 +153,7 @@ void test_diff_rename__not_exact_match(void) const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; git_tree *old_tree, *new_tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -205,7 +207,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - git_diff_list_free(diff); + git_diff_free(diff); /* git diff -M -C \ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ @@ -226,7 +228,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - git_diff_list_free(diff); + git_diff_free(diff); /* git diff -M -C --find-copies-harder --break-rewrites \ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ @@ -236,6 +238,8 @@ void test_diff_rename__not_exact_match(void) &diff, g_repo, old_tree, new_tree, &diffopts)); opts.flags = GIT_DIFF_FIND_ALL; + opts.break_rewrite_threshold = 70; + cl_git_pass(git_diff_find_similar(diff, &opts)); memset(&exp, 0, sizeof(exp)); @@ -249,7 +253,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - git_diff_list_free(diff); + git_diff_free(diff); /* == Changes ===================================================== * songofseven.txt -> untimely.txt (rename, convert to crlf) @@ -277,7 +281,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - git_diff_list_free(diff); + git_diff_free(diff); /* git diff -M -C \ * 1c068dee5790ef1580cfc4cd670915b48d790084 \ @@ -297,7 +301,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); /* git diff -M -C --find-copies-harder --break-rewrites \ * 1c068dee5790ef1580cfc4cd670915b48d790084 \ @@ -312,8 +316,8 @@ void test_diff_rename__not_exact_match(void) /* the default match algorithm is going to find the internal * whitespace differences in the lines of sixserving.txt to be - * significant enough that this will decide to split it into - * an ADD and a DELETE + * significant enough that this will decide to split it into an ADD + * and a DELETE */ memset(&exp, 0, sizeof(exp)); @@ -326,7 +330,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); /* git diff -M -C --find-copies-harder --break-rewrites \ * 1c068dee5790ef1580cfc4cd670915b48d790084 \ @@ -349,7 +353,7 @@ void test_diff_rename__not_exact_match(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(old_tree); git_tree_free(new_tree); @@ -360,7 +364,7 @@ void test_diff_rename__handles_small_files(void) const char *tree_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; @@ -384,7 +388,7 @@ void test_diff_rename__handles_small_files(void) GIT_DIFF_FIND_AND_BREAK_REWRITES; cl_git_pass(git_diff_find_similar(diff, &opts)); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); } @@ -396,7 +400,7 @@ void test_diff_rename__working_directory_changes(void) git_oid id; git_tree *tree; git_blob *blob; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -447,7 +451,7 @@ void test_diff_rename__working_directory_changes(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* rewrite files in the working directory with / without CRLF changes */ @@ -473,13 +477,14 @@ void test_diff_rename__working_directory_changes(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* try a different whitespace option */ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + opts.rename_threshold = 70; cl_git_pass(git_diff_find_similar(diff, &opts)); memset(&exp, 0, sizeof(exp)); @@ -491,7 +496,7 @@ void test_diff_rename__working_directory_changes(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* try a different matching option */ @@ -509,14 +514,14 @@ void test_diff_rename__working_directory_changes(void) cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - git_diff_list_free(diff); + git_diff_free(diff); /* again with exact match blob */ cl_git_pass(git_oid_fromstr(&id, blobsha)); cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); cl_git_pass(git_buf_set( - &content, git_blob_rawcontent(blob), git_blob_rawsize(blob))); + &content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob))); cl_git_rewritefile("renames/songof7cities.txt", content.ptr); git_blob_free(blob); @@ -540,7 +545,7 @@ void test_diff_rename__working_directory_changes(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_buf_free(&content); @@ -552,10 +557,10 @@ void test_diff_rename__patch(void) const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; git_tree *old_tree, *new_tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_diff_patch *patch; + git_patch *patch; const git_diff_delta *delta; char *text; const char *expected = "diff --git a/sixserving.txt b/ikeepsix.txt\nindex ad0a8e5..36020db 100644\n--- a/sixserving.txt\n+++ b/ikeepsix.txt\n@@ -1,3 +1,6 @@\n+I Keep Six Honest Serving-Men\n+=============================\n+\n I KEEP six honest serving-men\n (They taught me all I knew);\n Their names are What and Why and When\n@@ -21,4 +24,4 @@ She sends'em abroad on her own affairs,\n One million Hows, two million Wheres,\n And seven million Whys!\n \n- -- Rudyard Kipling\n+ -- Rudyard Kipling\n"; @@ -579,25 +584,26 @@ void test_diff_rename__patch(void) cl_assert_equal_i(4, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, 0)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); cl_assert_equal_i(GIT_DELTA_COPIED, (int)delta->status); - cl_git_pass(git_diff_patch_to_str(&text, patch)); + cl_git_pass(git_patch_to_str(&text, patch)); cl_assert_equal_s(expected, text); git__free(text); - git_diff_patch_free(patch); + git_patch_free(patch); - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, 1)); + cl_assert((delta = git_diff_get_delta(diff, 1)) != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, (int)delta->status); - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, 2)); + cl_assert((delta = git_diff_get_delta(diff, 2)) != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_git_pass(git_diff_get_patch(NULL, &delta, diff, 3)); + cl_assert((delta = git_diff_get_delta(diff, 3)) != NULL); cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(old_tree); git_tree_free(new_tree); } @@ -607,7 +613,7 @@ void test_diff_rename__file_exchange(void) git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -642,7 +648,7 @@ void test_diff_rename__file_exchange(void) cl_assert_equal_i(2, exp.files); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); @@ -655,7 +661,7 @@ void test_diff_rename__file_exchange_three(void) git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT, c3 = GIT_BUF_INIT; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -694,7 +700,7 @@ void test_diff_rename__file_exchange_three(void) cl_assert_equal_i(3, exp.files); cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); @@ -708,7 +714,7 @@ void test_diff_rename__file_partial_exchange(void) git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -747,7 +753,7 @@ void test_diff_rename__file_partial_exchange(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); @@ -760,7 +766,7 @@ void test_diff_rename__rename_and_copy_from_same_source(void) git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -804,7 +810,7 @@ void test_diff_rename__rename_and_copy_from_same_source(void) cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]); cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); @@ -817,7 +823,7 @@ void test_diff_rename__from_deleted_to_split(void) git_buf c1 = GIT_BUF_INIT; git_index *index; git_tree *tree; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; diff_expects exp; @@ -858,7 +864,7 @@ void test_diff_rename__from_deleted_to_split(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); @@ -868,6 +874,8 @@ void test_diff_rename__from_deleted_to_split(void) struct rename_expected { size_t len; + + unsigned int *status; const char **sources; const char **targets; @@ -882,7 +890,7 @@ int test_names_expected(const git_diff_delta *delta, float progress, void *p) cl_assert(expected->idx < expected->len); - cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED); + cl_assert_equal_i(delta->status, expected->status[expected->idx]); cl_assert(git__strcmp(expected->sources[expected->idx], delta->old_file.path) == 0); @@ -900,13 +908,14 @@ void test_diff_rename__rejected_match_can_match_others(void) git_index *index; git_tree *tree; git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; git_buf one = GIT_BUF_INIT, two = GIT_BUF_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; const char *sources[] = { "Class1.cs", "Class2.cs" }; const char *targets[] = { "ClassA.cs", "ClassB.cs" }; - struct rename_expected expect = { 2, sources, targets }; + struct rename_expected expect = { 2, status, sources, targets }; char *ptr; opts.checkout_strategy = GIT_CHECKOUT_FORCE; @@ -953,7 +962,7 @@ void test_diff_rename__rejected_match_can_match_others(void) cl_git_pass( git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); git_reference_free(head); @@ -985,12 +994,13 @@ void test_diff_rename__rejected_match_can_match_others_two(void) git_index *index; git_tree *tree; git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; - git_diff_list *diff; + git_diff *diff; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; const char *sources[] = { "a.txt", "b.txt" }; const char *targets[] = { "c.txt", "d.txt" }; - struct rename_expected expect = { 2, sources, targets }; + struct rename_expected expect = { 2, status, sources, targets }; opts.checkout_strategy = GIT_CHECKOUT_FORCE; @@ -1026,18 +1036,121 @@ void test_diff_rename__rejected_match_can_match_others_two(void) git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); cl_assert(expect.idx > 0); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(tree); git_index_free(index); git_reference_free(head); git_reference_free(selfsimilar); } +void test_diff_rename__rejected_match_can_match_others_three(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + /* Both cannot be renames from a.txt */ + unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED }; + const char *sources[] = { "0001.txt", "a.txt" }; + const char *targets[] = { "0001.txt", "0002.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar_two")); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_unlink("renames/a.txt")); + + cl_git_pass(git_index_remove_bypath(index, "a.txt")); + + write_similarity_file_two("renames/0001.txt", 7); + write_similarity_file_two("renames/0002.txt", 0); + + cl_git_pass(git_index_add_bypath(index, "0001.txt")); + cl_git_pass(git_index_add_bypath(index, "0002.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass( + git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); +} + +void test_diff_rename__can_rename_from_rewrite(void) +{ + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; + const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" }; + const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt")); + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass( + git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +} + void test_diff_rename__case_changes_are_split(void) { git_index *index; git_tree *tree; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; @@ -1070,7 +1183,7 @@ void test_diff_rename__case_changes_are_split(void) cl_assert_equal_i(1, exp.files); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_index_free(index); git_tree_free(tree); } @@ -1079,7 +1192,7 @@ void test_diff_rename__unmodified_can_be_renamed(void) { git_index *index; git_tree *tree; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; @@ -1118,7 +1231,56 @@ void test_diff_rename__unmodified_can_be_renamed(void) cl_assert_equal_i(1, exp.files); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_list_free(diff); + git_diff_free(diff); git_index_free(index); git_tree_free(tree); } + +void test_diff_rename__rewrite_on_single_file(void) +{ + git_index *index; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + git_index_free(index); +} diff --git a/tests/diff/submodules.c b/tests/diff/submodules.c new file mode 100644 index 000000000..24545b2c7 --- /dev/null +++ b/tests/diff/submodules.c @@ -0,0 +1,423 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "posix.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_submodules__initialize(void) +{ +} + +void test_diff_submodules__cleanup(void) +{ +} + +static void check_diff_patches_at_line( + git_diff *diff, const char **expected, const char *file, int line) +{ + const git_diff_delta *delta; + git_patch *patch = NULL; + size_t d, num_d = git_diff_num_deltas(diff); + char *patch_text; + + for (d = 0; d < num_d; ++d, git_patch_free(patch)) { + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + + if (delta->status == GIT_DELTA_UNMODIFIED) { + cl_assert_at_line(expected[d] == NULL, file, line); + continue; + } + + if (expected[d] && !strcmp(expected[d], "")) + continue; + if (expected[d] && !strcmp(expected[d], "")) { + cl_git_pass(git_patch_to_str(&patch_text, patch)); + cl_assert_at_line(!strcmp(expected[d], ""), file, line); + } + + cl_git_pass(git_patch_to_str(&patch_text, patch)); + + clar__assert_equal( + file, line, "expected diff did not match actual diff", 1, + "%s", expected[d], patch_text); + git__free(patch_text); + } + + cl_assert_at_line(expected[d] && !strcmp(expected[d], ""), file, line); +} + +#define check_diff_patches(diff, exp) \ + check_diff_patches_at_line(diff, exp, __FILE__, __LINE__) + +void test_diff_submodules__unmodified_submodule(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + NULL, /* added */ + NULL, /* ignored */ + "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ + NULL, /* testrepo.git */ + NULL, /* unmodified */ + NULL, /* untracked */ + "" + }; + + g_repo = setup_fixture_submodules(); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__dirty_submodule(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + NULL, /* added */ + NULL, /* ignored */ + "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ + "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ + NULL, /* unmodified */ + NULL, /* untracked */ + "" + }; + + g_repo = setup_fixture_submodules(); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__dirty_submodule_2(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL, *diff2 = NULL; + char *smpath = "testrepo"; + static const char *expected_none[] = { "" }; + static const char *expected_dirty[] = { + "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ + "" + }; + + g_repo = setup_fixture_submodules(); + + cl_git_pass(git_submodule_reload_all(g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_SHOW_UNTRACKED_CONTENT | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.old_prefix = "a"; opts.new_prefix = "b"; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_none); + git_diff_free(diff); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + + { + git_tree *head; + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + git_tree_free(head); + + check_diff_patches(diff, expected_dirty); + } + + git_diff_free(diff); + + cl_git_pass(git_submodule_reload_all(g_repo)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); +} + +void test_diff_submodules__submod2_index_to_wd(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ + "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + + g_repo = setup_fixture_submod2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__submod2_head_to_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_tree *head; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + "diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */ + "" + }; + + g_repo = setup_fixture_submod2(); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); + + git_tree_free(head); +} + +void test_diff_submodules__invalid_cache(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_submodule *sm; + char *smpath = "sm_changed_head"; + git_repository *smrepo; + git_index *smindex; + static const char *expected_baseline[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "" + }; + static const char *expected_unchanged[] = { "" }; + static const char *expected_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n", + "" + }; + static const char *expected_moved[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n", + "" + }; + static const char *expected_moved_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n", + "" + }; + + g_repo = setup_fixture_submod2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + /* baseline */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_baseline); + git_diff_free(diff); + + /* update index with new HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + cl_git_pass(git_submodule_add_to_index(sm, 1)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* create untracked file in submodule working directory */ + cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello"); + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* modify tracked file in submodule working directory */ + cl_git_append2file( + "submod2/sm_changed_head/file_to_modify", "\nmore stuff\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + cl_git_pass(git_submodule_reload_all(g_repo)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "file_to_modify")); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it"); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_ALL); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved_dirty); + git_diff_free(diff); + + p_unlink("submod2/sm_changed_head/new_around_here"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_free(diff); + + git_index_free(smindex); + git_repository_free(smrepo); +} + +void test_diff_submodules__diff_ignore_options(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_config *cfg; + static const char *expected_normal[] = { + "", /* .gitmodules */ + "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ + "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + static const char *expected_ignore_all[] = { + "", /* .gitmodules */ + "" + }; + static const char *expected_ignore_dirty[] = { + "", /* .gitmodules */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + + g_repo = setup_fixture_submod2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES; + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_free(diff); + + opts.ignore_submodules = 0; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_free(diff); + + git_config_free(cfg); +} diff --git a/tests-clar/diff/tree.c b/tests/diff/tree.c similarity index 89% rename from tests-clar/diff/tree.c rename to tests/diff/tree.c index f05c7869e..582174b8b 100644 --- a/tests-clar/diff/tree.c +++ b/tests/diff/tree.c @@ -3,15 +3,13 @@ static git_repository *g_repo = NULL; static git_diff_options opts; -static git_diff_list *diff; +static git_diff *diff; static git_tree *a, *b; static diff_expects expect; void test_diff_tree__initialize(void) { - GIT_INIT_STRUCTURE(&opts, GIT_DIFF_OPTIONS_VERSION); - /* The default context lines is set by _INIT which we can't use here */ - opts.context_lines = 3; + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); memset(&expect, 0, sizeof(expect)); @@ -22,7 +20,7 @@ void test_diff_tree__initialize(void) void test_diff_tree__cleanup(void) { - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(a); git_tree_free(b); @@ -65,7 +63,7 @@ void test_diff_tree__0(void) cl_assert_equal_i(24 + 1 + 5 + 5, expect.line_adds); cl_assert_equal_i(7 + 1, expect.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; memset(&expect, 0, sizeof(expect)); @@ -90,6 +88,10 @@ void test_diff_tree__0(void) git_tree_free(c); } +#define DIFF_OPTS(FLAGS, CTXT) \ + {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_DEFAULT, \ + {NULL,0}, NULL, NULL, (CTXT), 1} + void test_diff_tree__options(void) { /* grabbed a couple of commit oids from the history of the attr repo */ @@ -102,16 +104,16 @@ void test_diff_tree__options(void) int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 }; git_diff_options test_options[] = { /* a vs b tests */ - { 1, GIT_DIFF_NORMAL, 1, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_REVERSE, 2, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_FORCE_TEXT, 2, 1, NULL, NULL, {0} }, + DIFF_OPTS(GIT_DIFF_NORMAL, 1), + DIFF_OPTS(GIT_DIFF_NORMAL, 3), + DIFF_OPTS(GIT_DIFF_REVERSE, 2), + DIFF_OPTS(GIT_DIFF_FORCE_TEXT, 2), /* c vs d tests */ - { 1, GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_IGNORE_WHITESPACE, 3, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_IGNORE_WHITESPACE_EOL, 3, 1, NULL, NULL, {0} }, - { 1, GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1, 1, NULL, NULL, {0} }, + DIFF_OPTS(GIT_DIFF_NORMAL, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_EOL, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1), }; /* to generate these values: @@ -168,7 +170,7 @@ void test_diff_tree__options(void) cl_assert_equal_i(actual.line_adds, expected->line_adds); cl_assert_equal_i(actual.line_dels, expected->line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; } @@ -214,7 +216,7 @@ void test_diff_tree__merge(void) const char *b_commit = "370fe9ec22"; const char *c_commit = "f5b0af1fb4f5c"; git_tree *c; - git_diff_list *diff1 = NULL, *diff2 = NULL; + git_diff *diff1 = NULL, *diff2 = NULL; g_repo = cl_git_sandbox_init("attr"); @@ -230,7 +232,7 @@ void test_diff_tree__merge(void) cl_git_pass(git_diff_merge(diff1, diff2)); - git_diff_list_free(diff2); + git_diff_free(diff2); cl_git_pass(git_diff_foreach( diff1, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect)); @@ -247,19 +249,17 @@ void test_diff_tree__merge(void) cl_assert_equal_i(36, expect.line_adds); cl_assert_equal_i(22, expect.line_dels); - git_diff_list_free(diff1); + git_diff_free(diff1); } void test_diff_tree__larger_hunks(void) { const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; - size_t d, num_d, h, num_h, l, num_l, header_len, line_len; - const git_diff_delta *delta; - git_diff_patch *patch; - const git_diff_range *range; - const char *header, *line; - char origin; + size_t d, num_d, h, num_h, l, num_l; + git_patch *patch; + const git_diff_hunk *hunk; + const git_diff_line *line; g_repo = cl_git_sandbox_init("diff"); @@ -273,31 +273,27 @@ void test_diff_tree__larger_hunks(void) num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { - cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - cl_assert(patch && delta); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); - num_h = git_diff_patch_num_hunks(patch); + num_h = git_patch_num_hunks(patch); for (h = 0; h < num_h; h++) { - cl_git_pass(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, h)); + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); for (l = 0; l < num_l; ++l) { - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &line, &line_len, NULL, NULL, patch, h, l)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); cl_assert(line); } - cl_git_fail(git_diff_patch_get_line_in_hunk( - &origin, &line, &line_len, NULL, NULL, patch, h, num_l)); + cl_git_fail(git_patch_get_line_in_hunk(&line, patch, h, num_l)); } - cl_git_fail(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, num_h)); + cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); - git_diff_patch_free(patch); + git_patch_free(patch); } - cl_git_fail(git_diff_get_patch(&patch, &delta, diff, num_d)); + cl_git_fail(git_patch_from_diff(&patch, diff, num_d)); cl_assert_equal_i(2, (int)num_d); } @@ -487,7 +483,7 @@ void test_diff_tree__diff_configs(void) cl_assert_equal_i(7, expect.line_adds); cl_assert_equal_i(15, expect.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; set_config_int(g_repo, "diff.context", 1); @@ -507,7 +503,7 @@ void test_diff_tree__diff_configs(void) cl_assert_equal_i(7, expect.line_adds); cl_assert_equal_i(15, expect.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; set_config_int(g_repo, "diff.context", 0); diff --git a/tests-clar/diff/workdir.c b/tests/diff/workdir.c similarity index 77% rename from tests-clar/diff/workdir.c rename to tests/diff/workdir.c index 6a2504d09..7cc032232 100644 --- a/tests-clar/diff/workdir.c +++ b/tests/diff/workdir.c @@ -16,7 +16,7 @@ void test_diff_workdir__cleanup(void) void test_diff_workdir__to_index(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; int use_iterator; @@ -60,7 +60,61 @@ void test_diff_workdir__to_index(void) cl_assert_equal_i(5, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); +} + +void test_diff_workdir__to_index_with_assume_unchanged(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + const git_index_entry *iep; + git_index_entry ie; + + g_repo = cl_git_sandbox_init("status"); + + /* do initial diff */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(8, 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]); + git_diff_free(diff); + + /* mark a couple of entries with ASSUME_UNCHANGED */ + + cl_git_pass(git_repository_index(&idx, g_repo)); + + cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_IDXENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_IDXENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + /* redo diff and see that entries are skipped */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + git_diff_free(diff); + } void test_diff_workdir__to_tree(void) @@ -70,8 +124,8 @@ void test_diff_workdir__to_tree(void) const char *b_commit = "0017bd4ab1ec3"; /* the start */ git_tree *a, *b; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; - git_diff_list *diff2 = NULL; + git_diff *diff = NULL; + git_diff *diff2 = NULL; diff_expects exp; int use_iterator; @@ -119,7 +173,7 @@ void test_diff_workdir__to_tree(void) * do more apples-to-apples test comparison below. */ - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; memset(&exp, 0, sizeof(exp)); @@ -130,7 +184,7 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_list_free(diff2); + git_diff_free(diff2); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -157,7 +211,7 @@ void test_diff_workdir__to_tree(void) cl_assert_equal_i(5, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; memset(&exp, 0, sizeof(exp)); @@ -167,7 +221,7 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_list_free(diff2); + git_diff_free(diff2); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -194,7 +248,39 @@ void test_diff_workdir__to_tree(void) cl_assert_equal_i(4, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); + + /* Let's try that once more with a reversed diff */ + + opts.flags |= GIT_DIFF_REVERSE; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_dels); + cl_assert_equal_i(4, exp.line_adds); + + git_diff_free(diff); + + /* all done now */ git_tree_free(a); git_tree_free(b); @@ -203,7 +289,7 @@ void test_diff_workdir__to_tree(void) void test_diff_workdir__to_index_with_pathspec(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; char *pathspec = NULL; int use_iterator; @@ -235,7 +321,7 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); } - git_diff_list_free(diff); + git_diff_free(diff); pathspec = "modified_file"; @@ -258,7 +344,7 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); } - git_diff_list_free(diff); + git_diff_free(diff); pathspec = "subdir"; @@ -281,7 +367,7 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); } - git_diff_list_free(diff); + git_diff_free(diff); pathspec = "*_deleted"; @@ -304,12 +390,12 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); } - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_workdir__filemode_changes(void) { - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; int use_iterator; @@ -339,7 +425,7 @@ void test_diff_workdir__filemode_changes(void) cl_assert_equal_i(0, exp.hunks); } - git_diff_list_free(diff); + git_diff_free(diff); /* chmod file and test again */ @@ -362,14 +448,14 @@ void test_diff_workdir__filemode_changes(void) cl_assert_equal_i(0, exp.hunks); } - git_diff_list_free(diff); + git_diff_free(diff); cl_assert(cl_toggle_filemode("issue_592/a.txt")); } void test_diff_workdir__filemode_changes_with_filemode_false(void) { - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; if (!cl_is_chmod_supported()) @@ -391,7 +477,7 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); - git_diff_list_free(diff); + git_diff_free(diff); /* chmod file and test again */ @@ -407,7 +493,7 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); - git_diff_list_free(diff); + git_diff_free(diff); cl_assert(cl_toggle_filemode("issue_592/a.txt")); } @@ -415,7 +501,7 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void) void test_diff_workdir__head_index_and_workdir_all_differ(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff_i2t = NULL, *diff_w2i = NULL; + git_diff *diff_i2t = NULL, *diff_w2i = NULL; diff_expects exp; char *pathspec = "staged_changes_modified_file"; git_tree *tree; @@ -504,8 +590,8 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) cl_assert_equal_i(0, exp.line_dels); } - git_diff_list_free(diff_i2t); - git_diff_list_free(diff_w2i); + git_diff_free(diff_i2t); + git_diff_free(diff_w2i); git_tree_free(tree); } @@ -513,7 +599,7 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) void test_diff_workdir__eof_newline_changes(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; char *pathspec = "current_file"; int use_iterator; @@ -546,7 +632,7 @@ void test_diff_workdir__eof_newline_changes(void) cl_assert_equal_i(0, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); cl_git_append2file("status/current_file", "\n"); @@ -573,7 +659,7 @@ void test_diff_workdir__eof_newline_changes(void) cl_assert_equal_i(0, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); cl_git_rewritefile("status/current_file", "current_file"); @@ -600,7 +686,7 @@ void test_diff_workdir__eof_newline_changes(void) cl_assert_equal_i(2, exp.line_dels); } - git_diff_list_free(diff); + git_diff_free(diff); } /* PREPARATION OF TEST DATA @@ -673,7 +759,7 @@ void test_diff_workdir__larger_hunks(void) const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; git_tree *a, *b; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i, d, num_d, h, num_h, l, num_l, header_len, line_len; + size_t i, d, num_d, h, num_h, l, num_l; g_repo = cl_git_sandbox_init("diff"); @@ -684,11 +770,10 @@ void test_diff_workdir__larger_hunks(void) opts.interhunk_lines = 0; for (i = 0; i <= 2; ++i) { - git_diff_list *diff = NULL; - git_diff_patch *patch; - const git_diff_range *range; - const char *header, *line; - char origin; + git_diff *diff = NULL; + git_patch *patch; + const git_diff_hunk *hunk; + const git_diff_line *line; /* okay, this is a bit silly, but oh well */ switch (i) { @@ -707,33 +792,31 @@ void test_diff_workdir__larger_hunks(void) cl_assert_equal_i(2, (int)num_d); for (d = 0; d < num_d; ++d) { - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d)); + cl_git_pass(git_patch_from_diff(&patch, diff, d)); cl_assert(patch); - num_h = git_diff_patch_num_hunks(patch); + num_h = git_patch_num_hunks(patch); for (h = 0; h < num_h; h++) { - cl_git_pass(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, h)); + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); for (l = 0; l < num_l; ++l) { - cl_git_pass(git_diff_patch_get_line_in_hunk( - &origin, &line, &line_len, NULL, NULL, patch, h, l)); + cl_git_pass( + git_patch_get_line_in_hunk(&line, patch, h, l)); cl_assert(line); } /* confirm fail after the last item */ - cl_git_fail(git_diff_patch_get_line_in_hunk( - &origin, &line, &line_len, NULL, NULL, patch, h, num_l)); + cl_git_fail( + git_patch_get_line_in_hunk(&line, patch, h, num_l)); } /* confirm fail after the last item */ - cl_git_fail(git_diff_patch_get_hunk( - &range, &header, &header_len, &num_l, patch, num_h)); + cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); - git_diff_patch_free(patch); + git_patch_free(patch); } - git_diff_list_free(diff); + git_diff_free(diff); } git_tree_free(a); @@ -758,26 +841,18 @@ void test_diff_workdir__submodules(void) const char *a_commit = "873585b94bdeabccea991ea5e3ec1a277895b698"; git_tree *a; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); - - cl_fixture_cleanup("submod2_target"); + g_repo = setup_fixture_submod2(); a = resolve_commit_oid_to_tree(g_repo, a_commit); opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GIT_DIFF_INCLUDE_UNTRACKED_CONTENT; + GIT_DIFF_SHOW_UNTRACKED_CONTENT; cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); @@ -806,7 +881,7 @@ void test_diff_workdir__submodules(void) * only significant difference is that those Added items will show up * as Untracked items in the pure libgit2 diff. * - * Then add in the two extra untracked items "not" and "not-submodule" + * Then add in the two extra ignored items "not" and "not-submodule" * to get the 12 files reported here. */ @@ -815,8 +890,8 @@ void test_diff_workdir__submodules(void) 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(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(8, exp.file_status[GIT_DELTA_UNTRACKED]); /* the following numbers match "git diff 873585" exactly */ @@ -827,14 +902,14 @@ void test_diff_workdir__submodules(void) cl_assert_equal_i(30, exp.line_adds); cl_assert_equal_i(1, exp.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(a); } void test_diff_workdir__cannot_diff_against_a_bare_repository(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; git_tree *tree; g_repo = cl_git_sandbox_init("testrepo.git"); @@ -852,7 +927,7 @@ void test_diff_workdir__cannot_diff_against_a_bare_repository(void) void test_diff_workdir__to_null_tree(void) { - git_diff_list *diff; + git_diff *diff; diff_expects exp; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; @@ -870,12 +945,12 @@ void test_diff_workdir__to_null_tree(void) cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_workdir__checks_options_version(void) { - git_diff_list *diff; + git_diff *diff; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; const git_error *err; @@ -895,11 +970,11 @@ void test_diff_workdir__checks_options_version(void) void test_diff_workdir__can_diff_empty_file(void) { - git_diff_list *diff; + git_diff *diff; git_tree *tree; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; struct stat st; - git_diff_patch *patch; + git_patch *patch; g_repo = cl_git_sandbox_init("attr_index"); @@ -909,7 +984,7 @@ void test_diff_workdir__can_diff_empty_file(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); cl_assert_equal_i(2, (int)git_diff_num_deltas(diff)); - git_diff_list_free(diff); + git_diff_free(diff); /* empty contents of file */ @@ -920,9 +995,9 @@ void test_diff_workdir__can_diff_empty_file(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); /* diffs are: .gitattributes, README.txt, sub/sub/.gitattributes */ - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 1)); - git_diff_patch_free(patch); - git_diff_list_free(diff); + cl_git_pass(git_patch_from_diff(&patch, diff, 1)); + git_patch_free(patch); + git_diff_free(diff); /* remove a file altogether */ @@ -931,9 +1006,9 @@ void test_diff_workdir__can_diff_empty_file(void) cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 1)); - git_diff_patch_free(patch); - git_diff_list_free(diff); + cl_git_pass(git_patch_from_diff(&patch, diff, 1)); + git_patch_free(patch); + git_diff_free(diff); git_tree_free(tree); } @@ -941,7 +1016,7 @@ void test_diff_workdir__can_diff_empty_file(void) void test_diff_workdir__to_index_issue_1397(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; g_repo = cl_git_sandbox_init("issue_1397"); @@ -961,7 +1036,7 @@ void test_diff_workdir__to_index_issue_1397(void) cl_assert_equal_i(0, exp.hunks); cl_assert_equal_i(0, exp.lines); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; cl_git_rewritefile("issue_1397/crlf_file.txt", @@ -983,7 +1058,7 @@ void test_diff_workdir__to_index_issue_1397(void) cl_assert_equal_i(1, exp.line_adds); cl_assert_equal_i(1, exp.line_dels); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_workdir__to_tree_issue_1397(void) @@ -991,8 +1066,8 @@ void test_diff_workdir__to_tree_issue_1397(void) const char *a_commit = "7f483a738"; /* the current HEAD */ git_tree *a; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; - git_diff_list *diff2 = NULL; + git_diff *diff = NULL; + git_diff *diff2 = NULL; diff_expects exp; g_repo = cl_git_sandbox_init("issue_1397"); @@ -1014,13 +1089,13 @@ void test_diff_workdir__to_tree_issue_1397(void) cl_assert_equal_i(0, exp.hunks); cl_assert_equal_i(0, exp.lines); - git_diff_list_free(diff); + git_diff_free(diff); diff = NULL; cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_list_free(diff2); + git_diff_free(diff2); memset(&exp, 0, sizeof(exp)); cl_git_pass(git_diff_foreach( @@ -1030,14 +1105,14 @@ void test_diff_workdir__to_tree_issue_1397(void) cl_assert_equal_i(0, exp.hunks); cl_assert_equal_i(0, exp.lines); - git_diff_list_free(diff); + git_diff_free(diff); git_tree_free(a); } void test_diff_workdir__untracked_directory_scenarios(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; diff_expects exp; char *pathspec = NULL; static const char *files0[] = { @@ -1087,7 +1162,7 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* empty directory */ @@ -1107,7 +1182,7 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* empty directory in empty directory */ @@ -1127,7 +1202,7 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* directory with only ignored files */ @@ -1151,7 +1226,7 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* directory with ignored directory (contents irrelevant) */ @@ -1174,11 +1249,11 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* quick version avoids directory scan */ - opts.flags = opts.flags | GIT_DIFF_FAST_UNTRACKED_DIRS; + opts.flags = opts.flags | GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; memset(&exp, 0, sizeof(exp)); exp.names = files1; @@ -1194,11 +1269,11 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* directory with nested non-ignored content */ - opts.flags = opts.flags & ~GIT_DIFF_FAST_UNTRACKED_DIRS; + opts.flags = opts.flags & ~GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; cl_git_mkfile("status/subdir/directory/more/notignored", "not ignored deep under untracked\n"); @@ -1217,7 +1292,7 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); /* use RECURSE_UNTRACKED_DIRS to get actual untracked files (no ignores) */ @@ -1238,14 +1313,14 @@ void test_diff_workdir__untracked_directory_scenarios(void) cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_list_free(diff); + git_diff_free(diff); } void test_diff_workdir__untracked_directory_comes_last(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff = NULL; + git_diff *diff = NULL; g_repo = cl_git_sandbox_init("renames"); @@ -1263,5 +1338,153 @@ void test_diff_workdir__untracked_directory_comes_last(void) cl_assert(diff != NULL); - git_diff_list_free(diff); + git_diff_free(diff); +} + +void test_diff_workdir__untracked_with_bom(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + const git_diff_delta *delta; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_write2file("empty_standard_repo/bom.txt", + "\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664); + + opts.flags = + GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_assert((delta = git_diff_get_delta(diff, 0)) != NULL); + cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status); + + /* not known at this point + * cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + */ + + git_diff_free(diff); +} + +void test_diff_workdir__patience_diff(void) +{ + git_index *index; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_patch *patch = NULL; + char *as_str = NULL; + const char *expected_normal = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n-how to create\n-a patience diff\n I did not know\n how to create\n+a patience diff\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; + const char *expected_patience = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n+I did not know\n how to create\n a patience diff\n-I did not know\n-how to create\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_mkfile( + "empty_standard_repo/test.txt", + "When I wrote this\nI did not know\nhow to create\na patience diff\nI did not know\nhow to create\nanother problem\nI did not know\nhow to create\na minimal diff\n"); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "Base"); + git_index_free(index); + + cl_git_rewritefile( + "empty_standard_repo/test.txt", + "When I wrote this\nI did not know\nI did not know\nhow to create\na patience diff\nanother problem\na minimal diff\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&as_str, patch)); + + cl_assert_equal_s(expected_normal, as_str); + git__free(as_str); + git_patch_free(patch); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_PATIENCE; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_str(&as_str, patch)); + + cl_assert_equal_s(expected_patience, as_str); + git__free(as_str); + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_workdir__with_stale_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("status"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* make the in-memory index invalid */ + { + git_repository *r2; + git_index *idx2; + cl_git_pass(git_repository_open(&r2, "status")); + cl_git_pass(git_repository_index(&idx2, r2)); + cl_git_pass(git_index_add_bypath(idx2, "new_file")); + cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted")); + cl_git_pass(git_index_write(idx2)); + git_index_free(idx2); + git_repository_free(r2); + } + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; + + /* first try with index pointer which should prevent reload */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(17, 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(4, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + + /* now let's try without the index pointer which should trigger reload */ + + /* two files that were UNTRACKED should have become UNMODIFIED */ + /* one file that was UNMODIFIED should now have become UNTRACKED */ + /* one file that was DELETED should now be gone completely */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + git_diff_free(diff); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_index_free(idx); } diff --git a/tests-clar/fetchhead/fetchhead_data.h b/tests/fetchhead/fetchhead_data.h similarity index 100% rename from tests-clar/fetchhead/fetchhead_data.h rename to tests/fetchhead/fetchhead_data.h diff --git a/tests-clar/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c similarity index 100% rename from tests-clar/fetchhead/nonetwork.c rename to tests/fetchhead/nonetwork.c diff --git a/tests/filter/blob.c b/tests/filter/blob.c new file mode 100644 index 000000000..9600a9779 --- /dev/null +++ b/tests/filter/blob.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; + +void test_filter_blob__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol=lf\n"); +} + +void test_filter_blob__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_filter_blob__all_crlf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "a9a2e891")); /* all-crlf */ + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1)); + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1)); + + cl_assert_equal_s(ALL_CRLF_TEXT_AS_LF, buf.ptr); + + git_buf_free(&buf); + git_blob_free(blob); +} + +void test_filter_blob__ident(void) +{ + git_oid id; + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n"); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + git_blob_free(blob); + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n"); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.bin", 1)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identcrlf", 1)); + cl_assert_equal_s( + "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\r\nGoes there\r\n", buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identlf", 1)); + cl_assert_equal_s( + "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\nGoes there\n", buf.ptr); + + git_buf_free(&buf); + git_blob_free(blob); + +} diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c new file mode 100644 index 000000000..c9fb9cd7f --- /dev/null +++ b/tests/filter/crlf.c @@ -0,0 +1,71 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" + +static git_repository *g_repo = NULL; + +void test_filter_crlf__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); +} + +void test_filter_crlf__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_filter_crlf__to_worktree(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf in = { 0 }, out = { 0 }; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + in.ptr = "Some text\nRight here\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + +#ifdef GIT_WIN32 + cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr); +#else + cl_assert_equal_s("Some text\nRight here\n", out.ptr); +#endif + + git_filter_list_free(fl); + git_buf_free(&out); +} + +void test_filter_crlf__to_odb(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf in = { 0 }, out = { 0 }; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + in.ptr = "Some text\r\nRight here\r\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_s("Some text\nRight here\n", out.ptr); + + git_filter_list_free(fl); + git_buf_free(&out); +} diff --git a/tests/filter/crlf.h b/tests/filter/crlf.h new file mode 100644 index 000000000..9cb98ad4c --- /dev/null +++ b/tests/filter/crlf.h @@ -0,0 +1,25 @@ +#ifndef INCLUDE_filter_crlf_h__ +#define INCLUDE_filter_crlf_h__ + +/* + * file content for files in the resources/crlf repository + */ + +#define UTF8_BOM "\xEF\xBB\xBF" + +#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" +#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" +#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" + +#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW +#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" + +#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n" +#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW +#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n" +#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n" + +#endif diff --git a/tests/filter/custom.c b/tests/filter/custom.c new file mode 100644 index 000000000..a81885c28 --- /dev/null +++ b/tests/filter/custom.c @@ -0,0 +1,337 @@ +#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" + +/* going TO_WORKDIR, filters are executed low to high + * going TO_ODB, filters are executed high to low + */ +#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 +# define NEWLINE "\n" +#endif + +static char workdir_data[] = + "some simple" NEWLINE + "data" NEWLINE + "that will be" NEWLINE + "trivially" NEWLINE + "scrambled." NEWLINE; + +/* Represents the data above scrambled (bits flipped) after \r\n -> \n + * conversion, then bytewise reversed + */ +static unsigned char bitflipped_and_reversed_data[] = + { 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c, + 0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5, + 0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97, + 0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92, + 0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c }; + +#define BITFLIPPED_AND_REVERSED_DATA_LEN 51 + +static git_repository *g_repo = NULL; + +static void register_custom_filters(void); + +void test_filter_custom__initialize(void) +{ + register_custom_filters(); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "hero* bitflip reverse\n" + "herofile text\n" + "heroflip -reverse binary\n" + "*.bin binary\n"); +} + +void test_filter_custom__cleanup(void) +{ + cl_git_sandbox_cleanup(); + 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; + + if (!filters_registered) { + cl_git_pass(git_filter_register( + "bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY)); + + cl_git_pass(git_filter_register( + "reverse", create_reverse_filter("+reverse"), + REVERSE_FILTER_PRIORITY)); + + /* re-register reverse filter with standard filter=xyz priority */ + cl_git_pass(git_filter_register( + "pre-reverse", + create_reverse_filter("+prereverse"), + GIT_FILTER_DRIVER_PRIORITY)); + + filters_registered = 1; + } +} + + +void test_filter_custom__to_odb(void) +{ + git_filter_list *fl; + git_buf out = { 0 }; + git_buf in = GIT_BUF_INIT_CONST(workdir_data, strlen(workdir_data)); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB)); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); +} + +void test_filter_custom__to_workdir(void) +{ + git_filter_list *fl; + git_buf out = { 0 }; + git_buf in = GIT_BUF_INIT_CONST( + bitflipped_and_reversed_data, BITFLIPPED_AND_REVERSED_DATA_LEN); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE)); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + + cl_assert_equal_i(strlen(workdir_data), out.size); + + cl_assert_equal_i( + 0, memcmp(workdir_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_free(&out); +} + +void test_filter_custom__can_register_a_custom_filter_in_the_repository(void) +{ + git_filter_list *fl; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip, reverse, crlf */ + cl_assert_equal_sz(3, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip, reverse - possibly crlf depending on global config */ + { + size_t flen = git_filter_list_length(fl); + cl_assert(flen == 2 || flen == 3); + } + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero.bin", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip, reverse */ + cl_assert_equal_sz(2, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE)); + /* expect: bitflip (because of -reverse) */ + cl_assert_equal_sz(1, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "doesntapplytome.bin", GIT_FILTER_TO_WORKTREE)); + /* expect: none */ + cl_assert_equal_sz(0, git_filter_list_length(fl)); + git_filter_list_free(fl); +} + +void test_filter_custom__order_dependency(void) +{ + git_index *index; + git_blob *blob; + git_buf buf = { 0 }; + + /* so if ident and reverse are used together, an interesting thing + * happens - a reversed "$Id$" string is no longer going to trigger + * ident correctly. When checking out, the filters should be applied + * in order CLRF, then ident, then reverse, so ident expansion should + * work correctly. On check in, the content should be reversed, then + * ident, then CRLF filtered. Let's make sure that works... + */ + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "hero.*.rev-ident text ident prereverse eol=lf\n"); + + cl_git_mkfile( + "empty_standard_repo/hero.1.rev-ident", + "This is a test\n$Id$\nHave fun!\n"); + + cl_git_mkfile( + "empty_standard_repo/hero.2.rev-ident", + "Another test\n$dI$\nCrazy!\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "hero.1.rev-ident")); + cl_git_pass(git_index_add_bypath(index, "hero.2.rev-ident")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Filter chains\n"); + git_index_free(index); + + cl_git_pass(git_blob_lookup(&blob, g_repo, + & git_index_get_bypath(index, "hero.1.rev-ident", 0)->oid)); + cl_assert_equal_s( + "\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob)); + cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.1.rev-ident", 0)); + /* no expansion because id was reversed at checkin and now at ident + * time, reverse is not applied yet */ + cl_assert_equal_s( + "This is a test\n$Id$\nHave fun!\n", buf.ptr); + git_blob_free(blob); + + cl_git_pass(git_blob_lookup(&blob, g_repo, + & git_index_get_bypath(index, "hero.2.rev-ident", 0)->oid)); + cl_assert_equal_s( + "\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob)); + cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.2.rev-ident", 0)); + /* expansion because reverse was applied at checkin and at ident time, + * reverse is not applied yet */ + cl_assert_equal_s( + "Another test\n$59001fe193103b1016b27027c0c827d036fd0ac8 :dI$\nCrazy!\n", buf.ptr); + cl_assert_equal_i(0, git_oid_strcmp( + git_blob_id(blob), "8ca0df630d728c0c72072b6101b301391ef10095")); + git_blob_free(blob); + + git_buf_free(&buf); +} + +void test_filter_custom__filter_registry_failure_cases(void) +{ + git_filter fake = { GIT_FILTER_VERSION, 0 }; + + cl_assert_equal_i(GIT_EEXISTS, git_filter_register("bitflip", &fake, 0)); + + cl_git_fail(git_filter_unregister(GIT_FILTER_CRLF)); + cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT)); + cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter")); +} diff --git a/tests/filter/ident.c b/tests/filter/ident.c new file mode 100644 index 000000000..2c8e6abea --- /dev/null +++ b/tests/filter/ident.c @@ -0,0 +1,131 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" + +static git_repository *g_repo = NULL; + +void test_filter_ident__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); +} + +void test_filter_ident__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void add_blob_and_filter( + const char *data, + git_filter_list *fl, + const char *expected) +{ + git_oid id; + git_blob *blob; + git_buf out = { 0 }; + + cl_git_mkfile("crlf/identtest", data); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "identtest")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + + cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); + + cl_assert_equal_s(expected, out.ptr); + + git_blob_free(blob); + git_buf_free(&out); +} + +void test_filter_ident__to_worktree(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", fl, + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Junky$\nFun stuff\n", fl, + "Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9$\nFun stuff\n"); + add_blob_and_filter( + "$Id$\nAt the start\n", fl, + "$Id: b13415c767abc196fb95bd17070e8c1113e32160$\nAt the start\n"); + add_blob_and_filter( + "At the end\n$Id$", fl, + "At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272$"); + add_blob_and_filter( + "$Id$", fl, + "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$"); + add_blob_and_filter( + "$Id: Some sort of junk goes here$", fl, + "$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06$"); + + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} + +void test_filter_ident__to_odb(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Any junk you may have left here$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:x$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + + add_blob_and_filter( + "$Id$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n"); + + add_blob_and_filter( + "At the end\n$Id$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id", fl, "At the end\n$Id"); + add_blob_and_filter( + "At the end\n$IddI", fl, "At the end\n$IddI"); + + add_blob_and_filter("$Id$", fl, "$Id$"); + add_blob_and_filter("$Id: any$", fl, "$Id$"); + add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$"); + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} diff --git a/tests-clar/generate.py b/tests/generate.py similarity index 100% rename from tests-clar/generate.py rename to tests/generate.py diff --git a/tests-clar/index/addall.c b/tests/index/addall.c similarity index 82% rename from tests-clar/index/addall.c rename to tests/index/addall.c index fca6e77fa..44c51279d 100644 --- a/tests-clar/index/addall.c +++ b/tests/index/addall.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "../status/status_helpers.h" #include "posix.h" +#include "fileops.h" git_repository *g_repo = NULL; @@ -67,10 +68,11 @@ static int index_status_cb( return 0; } -static void check_status( +static void check_status_at_line( git_repository *repo, size_t index_adds, size_t index_dels, size_t index_mods, - size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores) + size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores, + const char *file, int line) { index_status_counts vals; @@ -78,15 +80,25 @@ static void check_status( cl_git_pass(git_status_foreach(repo, index_status_cb, &vals)); - cl_assert_equal_sz(index_adds, vals.index_adds); - cl_assert_equal_sz(index_dels, vals.index_dels); - cl_assert_equal_sz(index_mods, vals.index_mods); - cl_assert_equal_sz(wt_adds, vals.wt_adds); - cl_assert_equal_sz(wt_dels, vals.wt_dels); - cl_assert_equal_sz(wt_mods, vals.wt_mods); - cl_assert_equal_sz(ignores, vals.ignores); + clar__assert_equal( + file,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds); + clar__assert_equal( + file,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels); + clar__assert_equal( + file,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods); + clar__assert_equal( + file,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds); + clar__assert_equal( + file,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels); + clar__assert_equal( + file,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods); + clar__assert_equal( + file,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores); } +#define check_status(R,IA,ID,IM,WA,WD,WM,IG) \ + check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,__FILE__,__LINE__) + static void check_stat_data(git_index *index, const char *path, bool match) { const git_index_entry *entry; @@ -108,8 +120,11 @@ static void check_stat_data(git_index *index, const char *path, bool match) cl_assert(st.st_size == entry->file_size); cl_assert(st.st_uid == entry->uid); cl_assert(st.st_gid == entry->gid); - cl_assert_equal_b(st.st_mode & ~0777, entry->mode & ~0777); - cl_assert_equal_b(st.st_mode & 0111, entry->mode & 0111); + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o"); + if (cl_is_chmod_supported()) + cl_assert_equal_b( + GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode)); } else { /* most things will still match */ cl_assert(st.st_size != entry->file_size); @@ -117,37 +132,6 @@ static void check_stat_data(git_index *index, const char *path, bool match) } } -static void commit_index_to_head( - git_repository *repo, - const char *commit_message) -{ - git_index *index; - git_oid tree_id, commit_id; - git_tree *tree; - git_signature *sig; - git_commit *parent = NULL; - - git_revparse_single((git_object **)&parent, repo, "HEAD"); - /* it is okay if looking up the HEAD fails */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_index_write(index)); /* not needed, but might as well */ - git_index_free(index); - - cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); - - cl_git_pass(git_signature_now(&sig, "Testy McTester", "tt@tester.test")); - - cl_git_pass(git_commit_create_v( - &commit_id, repo, "HEAD", sig, sig, - NULL, commit_message, tree, parent ? 1 : 0, parent)); - - git_commit_free(parent); - git_tree_free(tree); - git_signature_free(sig); -} - void test_index_addall__repo_lifecycle(void) { int error; @@ -194,7 +178,7 @@ void test_index_addall__repo_lifecycle(void) check_stat_data(index, "addall/file.zzz", true); check_status(g_repo, 2, 0, 0, 3, 0, 0, 1); - commit_index_to_head(g_repo, "first commit"); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit"); check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); /* attempt to add an ignored file - does nothing */ @@ -241,7 +225,7 @@ void test_index_addall__repo_lifecycle(void) cl_git_pass(git_index_add_bypath(index, "file.zzz")); check_status(g_repo, 1, 0, 1, 3, 0, 0, 0); - commit_index_to_head(g_repo, "second commit"); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit"); check_status(g_repo, 0, 0, 0, 3, 0, 0, 0); cl_must_pass(p_unlink("addall/file.zzz")); diff --git a/tests-clar/index/conflicts.c b/tests/index/conflicts.c similarity index 100% rename from tests-clar/index/conflicts.c rename to tests/index/conflicts.c diff --git a/tests-clar/index/filemodes.c b/tests/index/filemodes.c similarity index 82% rename from tests-clar/index/filemodes.c rename to tests/index/filemodes.c index e56a9c069..013932696 100644 --- a/tests-clar/index/filemodes.c +++ b/tests/index/filemodes.c @@ -44,30 +44,36 @@ static void replace_file_with_mode( cl_git_pass(p_rename(path.ptr, backup)); cl_git_write2file( - path.ptr, content.ptr, O_WRONLY|O_CREAT|O_TRUNC, create_mode); + path.ptr, content.ptr, content.size, + O_WRONLY|O_CREAT|O_TRUNC, create_mode); git_buf_free(&path); git_buf_free(&content); } -static void add_and_check_mode( - git_index *index, const char *filename, unsigned int expect_mode) +#define add_and_check_mode(I,F,X) add_and_check_mode_(I,F,X,__FILE__,__LINE__) + +static void add_and_check_mode_( + git_index *index, const char *filename, unsigned int expect_mode, + const char *file, int line) { size_t pos; const git_index_entry *entry; cl_git_pass(git_index_add_bypath(index, filename)); - cl_assert(!git_index_find(&pos, index, filename)); + clar__assert(!git_index_find(&pos, index, filename), + file, line, "Cannot find index entry", NULL, 1); entry = git_index_get_byindex(index, pos); - cl_assert(entry->mode == expect_mode); + + clar__assert_equal(file, line, "Expected mode does not match index", + 1, "%07o", (unsigned int)entry->mode, (unsigned int)expect_mode); } void test_index_filemodes__untrusted(void) { git_index *index; - bool can_filemode = cl_is_chmod_supported(); cl_repo_set_bool(g_repo, "core.filemode", false); @@ -91,19 +97,14 @@ void test_index_filemodes__untrusted(void) add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); /* 5 - add new 0644 -> expect 0644 */ - cl_git_write2file("filemodes/new_off", "blah", + cl_git_write2file("filemodes/new_off", "blah", 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); - /* this test won't give predictable results on a platform - * that doesn't support filemodes correctly, so skip it. - */ - if (can_filemode) { - /* 6 - add 0755 -> expect 0755 */ - cl_git_write2file("filemodes/new_on", "blah", - O_WRONLY | O_CREAT | O_TRUNC, 0755); - add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); - } + /* 6 - add new 0755 -> expect 0644 if core.filemode == false */ + cl_git_write2file("filemodes/new_on", "blah", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0755); + add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB); git_index_free(index); } @@ -140,12 +141,12 @@ void test_index_filemodes__trusted(void) add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); /* 5 - add new 0644 -> expect 0644 */ - cl_git_write2file("filemodes/new_off", "blah", + cl_git_write2file("filemodes/new_off", "blah", 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); /* 6 - add 0755 -> expect 0755 */ - cl_git_write2file("filemodes/new_on", "blah", + cl_git_write2file("filemodes/new_on", "blah", 0, O_WRONLY | O_CREAT | O_TRUNC, 0755); add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); diff --git a/tests-clar/index/inmemory.c b/tests/index/inmemory.c similarity index 100% rename from tests-clar/index/inmemory.c rename to tests/index/inmemory.c diff --git a/tests-clar/index/names.c b/tests/index/names.c similarity index 60% rename from tests-clar/index/names.c rename to tests/index/names.c index 95a560ee4..9007b1b15 100644 --- a/tests-clar/index/names.c +++ b/tests/index/names.c @@ -63,7 +63,7 @@ void test_index_names__roundtrip(void) git_index_clear(repo_index); cl_assert(git_index_name_entrycount(repo_index) == 0); - cl_git_pass(git_index_read(repo_index)); + cl_git_pass(git_index_read(repo_index, true)); cl_assert(git_index_name_entrycount(repo_index) == 3); conflict_name = git_index_name_get_byindex(repo_index, 0); @@ -80,5 +80,69 @@ void test_index_names__roundtrip(void) cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); cl_assert(conflict_name->ours == NULL); cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); - +} + +void test_index_names__cleaned_on_reset_hard(void) +{ + git_object *target; + + retrieve_target_from_oid(&target, repo, "3a34580a35add43a4cf361e8e9a30060a905c876"); + + test_index_names__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); + cl_assert(git_index_name_entrycount(repo_index) == 0); + + git_object_free(target); +} + +void test_index_names__cleaned_on_reset_mixed(void) +{ + git_object *target; + + retrieve_target_from_oid(&target, repo, "3a34580a35add43a4cf361e8e9a30060a905c876"); + + test_index_names__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED)); + cl_assert(git_index_name_entrycount(repo_index) == 0); + + git_object_free(target); +} + +void test_index_names__cleaned_on_checkout_tree(void) +{ + git_oid oid; + git_object *obj; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + git_reference_name_to_id(&oid, repo, "refs/heads/master"); + git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY); + git_checkout_tree(repo, obj, &opts); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); + + git_object_free(obj); +} + +void test_index_names__cleaned_on_checkout_head(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + git_checkout_head(repo, &opts); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); +} + +void test_index_names__retained_on_checkout_index(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + git_checkout_index(repo, repo_index, &opts); + cl_assert(git_index_name_entrycount(repo_index) > 0); } diff --git a/tests-clar/index/read_tree.c b/tests/index/read_tree.c similarity index 100% rename from tests-clar/index/read_tree.c rename to tests/index/read_tree.c diff --git a/tests-clar/index/rename.c b/tests/index/rename.c similarity index 100% rename from tests-clar/index/rename.c rename to tests/index/rename.c diff --git a/tests-clar/index/reuc.c b/tests/index/reuc.c similarity index 99% rename from tests-clar/index/reuc.c rename to tests/index/reuc.c index 69ed4a933..a18d5602e 100644 --- a/tests-clar/index/reuc.c +++ b/tests/index/reuc.c @@ -276,8 +276,6 @@ void test_index_reuc__write(void) 0100644, &their_oid)); cl_git_pass(git_index_write(repo_index)); - - cl_git_pass(git_index_read(repo_index)); cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); /* ensure sort order was round-tripped correct */ diff --git a/tests-clar/index/stage.c b/tests/index/stage.c similarity index 100% rename from tests-clar/index/stage.c rename to tests/index/stage.c diff --git a/tests-clar/index/tests.c b/tests/index/tests.c similarity index 76% rename from tests-clar/index/tests.c rename to tests/index/tests.c index 1bc5e6a07..5a05bd8a9 100644 --- a/tests-clar/index/tests.c +++ b/tests/index/tests.c @@ -6,9 +6,10 @@ static const size_t index_entry_count_2 = 1437; #define TEST_INDEX_PATH cl_fixture("testrepo.git/index") #define TEST_INDEX2_PATH cl_fixture("gitgit.index") #define TEST_INDEXBIG_PATH cl_fixture("big.index") +#define TEST_INDEXBAD_PATH cl_fixture("bad.index") -// Suite data +/* Suite data */ struct test_entry { size_t index; char path[128]; @@ -24,7 +25,7 @@ static struct test_entry test_entries[] = { {48, "src/revobject.h", 1448, 0x4C3F7FE2} }; -// Helpers +/* Helpers */ static void copy_file(const char *src, const char *dst) { git_buf source_buf = GIT_BUF_INIT; @@ -32,7 +33,7 @@ static void copy_file(const char *src, const char *dst) cl_git_pass(git_futils_readbuffer(&source_buf, src)); - dst_fd = git_futils_creat_withpath(dst, 0777, 0666); //-V536 + dst_fd = git_futils_creat_withpath(dst, 0777, 0666); /* -V536 */ if (dst_fd < 0) goto cleanup; @@ -66,7 +67,7 @@ static void files_are_equal(const char *a, const char *b) } -// Fixture setup and teardown +/* Fixture setup and teardown */ void test_index_tests__initialize(void) { } @@ -99,11 +100,11 @@ void test_index_tests__default_test_index(void) entries = (git_index_entry **)index->entries.contents; for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - git_index_entry *e = entries[test_entries[i].index]; + git_index_entry *e = entries[test_entries[i].index]; - cl_assert_equal_s(e->path, test_entries[i].path); - cl_assert(e->mtime.seconds == test_entries[i].mtime); - cl_assert(e->file_size == test_entries[i].file_size); + cl_assert_equal_s(e->path, test_entries[i].path); + cl_assert(e->mtime.seconds == test_entries[i].mtime); + cl_assert(e->file_size == test_entries[i].file_size); } git_index_free(index); @@ -131,10 +132,10 @@ void test_index_tests__find_in_existing(void) cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - size_t idx; + size_t idx; - cl_assert(!git_index_find(&idx, index, test_entries[i].path)); - cl_assert(idx == test_entries[i].index); + cl_assert(!git_index_find(&idx, index, test_entries[i].path)); + cl_assert(idx == test_entries[i].index); } git_index_free(index); @@ -148,7 +149,7 @@ void test_index_tests__find_in_empty(void) cl_git_pass(git_index_open(&index, "fake-index")); for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - cl_assert(GIT_ENOTFOUND == git_index_find(NULL, index, test_entries[i].path)); + cl_assert(GIT_ENOTFOUND == git_index_find(NULL, index, test_entries[i].path)); } git_index_free(index); @@ -173,7 +174,8 @@ void test_index_tests__write(void) void test_index_tests__sort0(void) { - // sort the entires in an index + /* sort the entires in an index */ + /* * TODO: This no longer applies: * index sorting in Git uses some specific changes to the way @@ -187,7 +189,7 @@ void test_index_tests__sort0(void) void test_index_tests__sort1(void) { - // sort the entires in an empty index + /* sort the entires in an empty index */ git_index *index; cl_git_pass(git_index_open(&index, "fake-index")); @@ -223,9 +225,9 @@ void test_index_tests__add(void) /* Create a new file in the working directory */ cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); - cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0)); + cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666)); cl_git_pass(git_filebuf_write(&file, "hey there\n", 10)); - cl_git_pass(git_filebuf_commit(&file, 0666)); + cl_git_pass(git_filebuf_commit(&file)); /* Store the expected hash of the file/blob * This has been generated by executing the following @@ -349,14 +351,14 @@ void test_index_tests__remove_entry(void) cl_git_pass(git_index_add_bypath(index, "hello")); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert(git_index_entrycount(index) == 1); cl_assert(git_index_get_bypath(index, "hello", 0) != NULL); cl_git_pass(git_index_remove(index, "hello", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert(git_index_entrycount(index) == 0); cl_assert(git_index_get_bypath(index, "hello", 0) == NULL); @@ -388,7 +390,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_add_bypath(index, "b.txt")); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(4, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); @@ -397,7 +399,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_remove(index, "a/1.txt", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(3, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); @@ -406,7 +408,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_remove_directory(index, "a", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(1, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL); @@ -459,3 +461,85 @@ void test_index_tests__preserves_case(void) git_repository_free(repo); } +void test_index_tests__elocked(void) +{ + git_repository *repo; + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + const git_error *err; + int error; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + /* Lock the index file so we fail to lock it */ + cl_git_pass(git_filebuf_open(&file, index->index_file_path, 0, 0666)); + error = git_index_write(index); + cl_assert_equal_i(GIT_ELOCKED, error); + + err = giterr_last(); + cl_assert_equal_i(err->klass, GITERR_INDEX); + + git_filebuf_cleanup(&file); + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__reload_from_disk(void) +{ + git_repository *repo; + git_index *read_index; + git_index *write_index; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_futils_mkdir("./myrepo", NULL, 0777, GIT_MKDIR_PATH)); + cl_git_mkfile("./myrepo/a.txt", "a\n"); + cl_git_mkfile("./myrepo/b.txt", "b\n"); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&write_index, repo)); + cl_assert_equal_i(false, write_index->on_disk); + + cl_git_pass(git_index_open(&read_index, write_index->index_file_path)); + cl_assert_equal_i(false, read_index->on_disk); + + /* Stage two new files agaisnt the write_index */ + cl_git_pass(git_index_add_bypath(write_index, "a.txt")); + cl_git_pass(git_index_add_bypath(write_index, "b.txt")); + + cl_assert_equal_sz(2, git_index_entrycount(write_index)); + + /* Persist the index changes to disk */ + cl_git_pass(git_index_write(write_index)); + cl_assert_equal_i(true, write_index->on_disk); + + /* Sync the changes back into the read_index */ + cl_assert_equal_sz(0, git_index_entrycount(read_index)); + + cl_git_pass(git_index_read(read_index, true)); + cl_assert_equal_i(true, read_index->on_disk); + + cl_assert_equal_sz(2, git_index_entrycount(read_index)); + + /* Remove the index file from the filesystem */ + cl_git_pass(p_unlink(write_index->index_file_path)); + + /* Sync the changes back into the read_index */ + cl_git_pass(git_index_read(read_index, true)); + cl_assert_equal_i(false, read_index->on_disk); + cl_assert_equal_sz(0, git_index_entrycount(read_index)); + + git_index_free(read_index); + git_index_free(write_index); + git_repository_free(repo); +} + +void test_index_tests__corrupted_extension(void) +{ + git_index *index; + + cl_git_fail_with(git_index_open(&index, TEST_INDEXBAD_PATH), GIT_ERROR); +} diff --git a/tests-clar/main.c b/tests/main.c similarity index 100% rename from tests-clar/main.c rename to tests/main.c diff --git a/tests-clar/merge/merge_helpers.c b/tests/merge/merge_helpers.c similarity index 90% rename from tests-clar/merge/merge_helpers.c rename to tests/merge/merge_helpers.c index e4092787c..43619be0d 100644 --- a/tests-clar/merge/merge_helpers.c +++ b/tests/merge/merge_helpers.c @@ -52,6 +52,29 @@ int merge_trees_from_branches( return 0; } +int merge_branches(git_merge_result **result, git_repository *repo, const char *ours_branch, const char *theirs_branch, git_merge_opts *opts) +{ + git_reference *head_ref, *theirs_ref; + git_merge_head *theirs_head; + git_checkout_opts head_checkout_opts = GIT_CHECKOUT_OPTS_INIT; + + head_checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_symbolic_create(&head_ref, repo, "HEAD", ours_branch, 1)); + cl_git_pass(git_checkout_head(repo, &head_checkout_opts)); + + cl_git_pass(git_reference_lookup(&theirs_ref, repo, theirs_branch)); + cl_git_pass(git_merge_head_from_ref(&theirs_head, repo, theirs_ref)); + + cl_git_pass(git_merge(result, repo, (const git_merge_head **)&theirs_head, 1, opts)); + + git_reference_free(head_ref); + git_reference_free(theirs_ref); + git_merge_head_free(theirs_head); + + return 0; +} + void merge__dump_index_entries(git_vector *index_entries) { size_t i; @@ -291,7 +314,7 @@ int merge_test_workdir(git_repository *repo, const struct merge_index_entry expe git_buf wd = GIT_BUF_INIT; git_buf_puts(&wd, repo->workdir); - git_path_direach(&wd, dircount, &actual_len); + git_path_direach(&wd, 0, dircount, &actual_len); if (actual_len != expected_len) return 0; diff --git a/tests-clar/merge/merge_helpers.h b/tests/merge/merge_helpers.h similarity index 91% rename from tests-clar/merge/merge_helpers.h rename to tests/merge/merge_helpers.h index cb718e01a..ae3274437 100644 --- a/tests-clar/merge/merge_helpers.h +++ b/tests/merge/merge_helpers.h @@ -44,6 +44,9 @@ int merge_trees_from_branches( const char *ours_name, const char *theirs_name, git_merge_tree_opts *opts); +int merge_branches(git_merge_result **result, git_repository *repo, + const char *ours_branch, const char *theirs_branch, git_merge_opts *opts); + int merge_test_diff_list(git_merge_diff_list *diff_list, const struct merge_index_entry expected[], size_t expected_len); int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len); diff --git a/tests-clar/merge/trees/automerge.c b/tests/merge/trees/automerge.c similarity index 99% rename from tests-clar/merge/trees/automerge.c rename to tests/merge/trees/automerge.c index 04a7beff6..746ce5068 100644 --- a/tests-clar/merge/trees/automerge.c +++ b/tests/merge/trees/automerge.c @@ -122,7 +122,7 @@ void test_merge_trees_automerge__automerge(void) cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->oid, GIT_OBJ_BLOB)); - cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, entry->file_size) == 0); + cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); git_index_free(index); git_blob_free(blob); diff --git a/tests-clar/merge/trees/modeconflict.c b/tests/merge/trees/modeconflict.c similarity index 100% rename from tests-clar/merge/trees/modeconflict.c rename to tests/merge/trees/modeconflict.c diff --git a/tests-clar/merge/trees/renames.c b/tests/merge/trees/renames.c similarity index 100% rename from tests-clar/merge/trees/renames.c rename to tests/merge/trees/renames.c diff --git a/tests-clar/merge/trees/treediff.c b/tests/merge/trees/treediff.c similarity index 100% rename from tests-clar/merge/trees/treediff.c rename to tests/merge/trees/treediff.c diff --git a/tests-clar/merge/trees/trivial.c b/tests/merge/trees/trivial.c similarity index 100% rename from tests-clar/merge/trees/trivial.c rename to tests/merge/trees/trivial.c diff --git a/tests/merge/workdir/fastforward.c b/tests/merge/workdir/fastforward.c new file mode 100644 index 000000000..861f38354 --- /dev/null +++ b/tests/merge/workdir/fastforward.c @@ -0,0 +1,148 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "git2/sys/index.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "refs.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define THEIRS_FASTFORWARD_BRANCH "ff_branch" +#define THEIRS_FASTFORWARD_OID "fd89f8cffb663ac89095a0f9764902e93ceaca6a" + +#define THEIRS_NOFASTFORWARD_BRANCH "branch" +#define THEIRS_NOFASTFORWARD_OID "7cb63eed597130ba4abb87b3e544b85021905520" + + +// Fixture setup and teardown +void test_merge_workdir_fastforward__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_fastforward__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static git_merge_result *merge_fastforward_branch(int flags) +{ + git_reference *their_ref; + git_merge_head *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + opts.merge_flags = flags; + + cl_git_pass(git_reference_lookup(&their_ref, repo, GIT_REFS_HEADS_DIR THEIRS_FASTFORWARD_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, their_ref)); + + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + git_merge_head_free(their_heads[0]); + git_reference_free(their_ref); + + return result; +} + +void test_merge_workdir_fastforward__fastforward(void) +{ + git_merge_result *result; + git_oid expected, ff_oid; + + cl_git_pass(git_oid_fromstr(&expected, THEIRS_FASTFORWARD_OID)); + + cl_assert(result = merge_fastforward_branch(0)); + cl_assert(git_merge_result_is_fastforward(result)); + cl_git_pass(git_merge_result_fastforward_oid(&ff_oid, result)); + cl_assert(git_oid_cmp(&ff_oid, &expected) == 0); + + git_merge_result_free(result); +} + +void test_merge_workdir_fastforward__fastforward_only(void) +{ + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + git_reference *their_ref; + git_merge_head *their_head; + int error; + + opts.merge_flags = GIT_MERGE_FASTFORWARD_ONLY; + + cl_git_pass(git_reference_lookup(&their_ref, repo, GIT_REFS_HEADS_DIR THEIRS_NOFASTFORWARD_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref)); + + cl_git_fail((error = git_merge(&result, repo, (const git_merge_head **)&their_head, 1, &opts))); + cl_assert(error == GIT_ENONFASTFORWARD); + + git_merge_head_free(their_head); + git_reference_free(their_ref); +} + +void test_merge_workdir_fastforward__no_fastforward(void) +{ + git_merge_result *result; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "bd9cb4cd0a770cb9adcb5fce212142ef40ea1c35", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + { 0100644, "364bbe4ce80c7bd31e6307dce77d46e3e1759fb3", 0, "new-in-ff.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_assert(result = merge_fastforward_branch(GIT_MERGE_NO_FASTFORWARD)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + git_merge_result_free(result); +} + +void test_merge_workdir_fastforward__uptodate(void) +{ + git_reference *their_ref; + git_merge_head *their_heads[1]; + git_merge_result *result; + + cl_git_pass(git_reference_lookup(&their_ref, repo, GIT_HEAD_FILE)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, their_ref)); + + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, NULL)); + + cl_assert(git_merge_result_is_uptodate(result)); + + git_merge_head_free(their_heads[0]); + git_reference_free(their_ref); + git_merge_result_free(result); +} + +void test_merge_workdir_fastforward__uptodate_merging_prev_commit(void) +{ + git_oid their_oid; + git_merge_head *their_heads[1]; + git_merge_result *result; + + cl_git_pass(git_oid_fromstr(&their_oid, "c607fc30883e335def28cd686b51f6cfa02b06ec")); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &their_oid)); + + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, NULL)); + + cl_assert(git_merge_result_is_uptodate(result)); + + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} + diff --git a/tests/merge/workdir/renames.c b/tests/merge/workdir/renames.c new file mode 100644 index 000000000..d38397983 --- /dev/null +++ b/tests/merge/workdir/renames.c @@ -0,0 +1,156 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "buffer.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "fileops.h" +#include "refs.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define BRANCH_RENAME_OURS "rename_conflict_ours" +#define BRANCH_RENAME_THEIRS "rename_conflict_theirs" + +// Fixture setup and teardown +void test_merge_workdir_renames__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_workdir_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_workdir_renames__renames(void) +{ + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, + }; + + opts.merge_tree_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_tree_opts.rename_threshold = 50; + + cl_git_pass(merge_branches(&result, repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &opts)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); + + git_merge_result_free(result); +} + +void test_merge_workdir_renames__ours(void) +{ + git_index *index; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" }, + }; + + opts.merge_tree_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_tree_opts.rename_threshold = 50; + opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + cl_git_pass(merge_branches(&result, repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &opts)); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write(index)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 20)); + + git_merge_result_free(result); + git_index_free(index); +} + +void test_merge_workdir_renames__similar(void) +{ + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + /* + * Note: this differs slightly from the core git merge result - there, 4a is + * tracked as a rename/delete instead of a rename/add and the theirs side + * is not placed in workdir in any form. + */ + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, + }; + + opts.merge_tree_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES; + opts.merge_tree_opts.rename_threshold = 50; + + cl_git_pass(merge_branches(&result, repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &opts)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); + + git_merge_result_free(result); +} + diff --git a/tests-clar/merge/workdir/setup.c b/tests/merge/workdir/setup.c similarity index 92% rename from tests-clar/merge/workdir/setup.c rename to tests/merge/workdir/setup.c index 1c8403221..870d55ef2 100644 --- a/tests-clar/merge/workdir/setup.c +++ b/tests/merge/workdir/setup.c @@ -71,7 +71,7 @@ static void write_file_contents(const char *filename, const char *output) git_buf_free(&file_path_buf); } -/* git merge --no-ff octo1 */ +/* git merge octo1 */ void test_merge_workdir_setup__one_branch(void) { git_oid our_oid; @@ -97,7 +97,33 @@ void test_merge_workdir_setup__one_branch(void) git_merge_head_free(their_heads[0]); } -/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */ +/* git merge --no-ff octo1 */ +void test_merge_workdir_setup__no_fastforward(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_merge_head *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, GIT_MERGE_NO_FASTFORWARD)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 */ void test_merge_workdir_setup__one_oid(void) { git_oid our_oid; @@ -964,3 +990,68 @@ void test_merge_workdir_setup__head_foreach_octopus(void) cl_assert(cb_data.i == cb_data.len); } + +void test_merge_workdir_setup__retained_after_success(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_merge_head *our_head, *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + opts.merge_flags |= GIT_MERGE_NO_FASTFORWARD; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)&their_heads[0], 1, &opts)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} + +void test_merge_workdir_setup__removed_after_failure(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_merge_head *our_head, *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + opts.merge_flags |= GIT_MERGE_NO_FASTFORWARD; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_rewritefile("merge-resolve/new-in-octo1.txt", + "Conflicting file!\n\nMerge will fail!\n"); + + cl_git_fail(git_merge( + &result, repo, (const git_merge_head **)&their_heads[0], 1, &opts)); + + cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_HEAD_FILE)); + cl_assert(!git_path_exists("merge-resolve/" GIT_ORIG_HEAD_FILE)); + cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_MODE_FILE)); + cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_MSG_FILE)); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c new file mode 100644 index 000000000..4a3b86ee4 --- /dev/null +++ b/tests/merge/workdir/simple.c @@ -0,0 +1,491 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "buffer.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "refs.h" +#include "fileops.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define THEIRS_SIMPLE_BRANCH "branch" +#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define THEIRS_UNRELATED_BRANCH "unrelated" +#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f" +#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50" + +#define OURS_DIRECTORY_FILE "df_side1" +#define THEIRS_DIRECTORY_FILE "fc90237dc4891fa6c69827fc465632225e391618" + + +/* Non-conflicting files, index entries are common to every merge operation */ +#define ADDED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, \ + "added-in-master.txt" } +#define AUTOMERGEABLE_INDEX_ENTRY \ + { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, \ + "automergeable.txt" } +#define CHANGED_IN_BRANCH_INDEX_ENTRY \ + { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, \ + "changed-in-branch.txt" } +#define CHANGED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, \ + "changed-in-master.txt" } +#define UNCHANGED_INDEX_ENTRY \ + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, \ + "unchanged.txt" } + +/* Unrelated files */ +#define UNRELATED_NEW1 \ + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, \ + "new-in-unrelated1.txt" } +#define UNRELATED_NEW2 \ + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, \ + "new-in-unrelated2.txt" } + +/* Expected REUC entries */ +#define AUTOMERGEABLE_REUC_ENTRY \ + { "automergeable.txt", 0100644, 0100644, 0100644, \ + "6212c31dab5e482247d7977e4f0dd3601decf13b", \ + "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ + "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" } +#define CONFLICTING_REUC_ENTRY \ + { "conflicting.txt", 0100644, 0100644, 0100644, \ + "d427e0b2e138501a3d15cc376077a3631e15bd46", \ + "4e886e602529caa9ab11d71f86634bd1b6e0de10", \ + "2bd0a343aeef7a2cf0d158478966a6e587ff3863" } +#define REMOVED_IN_BRANCH_REUC_ENTRY \ + { "removed-in-branch.txt", 0100644, 0100644, 0, \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "" } +#define REMOVED_IN_MASTER_REUC_ENTRY \ + { "removed-in-master.txt", 0100644, 0, 0100644, \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ + "", \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" } + +#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_DIFF3_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +// Fixture setup and teardown +void test_merge_workdir_simple__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_simple__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static git_merge_result *merge_simple_branch(int automerge_flags, int checkout_strategy) +{ + git_oid their_oids[1]; + git_merge_head *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_SIMPLE_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &their_oids[0])); + + opts.merge_tree_opts.automerge_flags = automerge_flags; + opts.checkout_opts.checkout_strategy = checkout_strategy; + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + git_merge_head_free(their_heads[0]); + + return result; +} + +static void set_core_autocrlf_to(git_repository *repo, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); + + git_config_free(cfg); +} + +void test_merge_workdir_simple__automerge(void) +{ + git_index *index; + const git_index_entry *entry; + git_merge_result *result; + git_buf automergeable_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + + set_core_autocrlf_to(repo, false); + + cl_assert(result = merge_simple_branch(0, 0)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_git_pass(git_futils_readbuffer(&automergeable_buf, + TEST_REPO_PATH "/automergeable.txt")); + cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE) == 0); + git_buf_free(&automergeable_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_merge_result_free(result); + + git_repository_index(&index, repo); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); + + git_index_free(index); +} + +void test_merge_workdir_simple__automerge_crlf(void) +{ +#ifdef GIT_WIN32 + git_index *index; + const git_index_entry *entry; + + git_merge_result *result; + git_buf automergeable_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, true); + + cl_assert(result = merge_simple_branch(0, 0)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_git_pass(git_futils_readbuffer(&automergeable_buf, + TEST_REPO_PATH "/automergeable.txt")); + cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE_CRLF) == 0); + git_buf_free(&automergeable_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_merge_result_free(result); + + git_repository_index(&index, repo); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE_CRLF)); + + git_index_free(index); +#endif /* GIT_WIN32 */ +} + +void test_merge_workdir_simple__diff3(void) +{ + git_merge_result *result; + git_buf conflicting_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_assert(result = merge_simple_branch(0, 0)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0); + git_buf_free(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_merge_result_free(result); +} + +void test_merge_workdir_simple__checkout_ours(void) +{ + git_merge_result *result; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_assert(result = merge_simple_branch(0, GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + cl_assert(git_path_exists(TEST_REPO_PATH "/conflicting.txt")); + + git_merge_result_free(result); +} + +void test_merge_workdir_simple__favor_ours(void) +{ + git_merge_result *result; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + cl_assert(result = merge_simple_branch(GIT_MERGE_AUTOMERGE_FAVOR_OURS, 0)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); + + git_merge_result_free(result); +} + +void test_merge_workdir_simple__favor_theirs(void) +{ + git_merge_result *result; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + cl_assert(result = merge_simple_branch(GIT_MERGE_AUTOMERGE_FAVOR_THEIRS, 0)); + cl_assert(!git_merge_result_is_fastforward(result)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); + + git_merge_result_free(result); +} + +void test_merge_workdir_simple__directory_file(void) +{ + git_reference *head; + git_oid their_oids[1], head_commit_id; + git_merge_head *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + git_commit *head_commit; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, + { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, + { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, + { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, + { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, + { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, + { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, + { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, + { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, + { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, + { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, + { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, + { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, + { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, + { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, + { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, + { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, + { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, + { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, + { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, + }; + + cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_DIR OURS_DIRECTORY_FILE, 1)); + cl_git_pass(git_reference_name_to_id(&head_commit_id, repo, GIT_HEAD_FILE)); + cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_id)); + cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD)); + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_DIRECTORY_FILE)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &their_oids[0])); + + opts.merge_tree_opts.automerge_flags = 0; + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 20)); + + git_reference_free(head); + git_commit_free(head_commit); + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} + +void test_merge_workdir_simple__unrelated(void) +{ + git_oid their_oids[1]; + git_merge_head *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_PARENT)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &their_oids[0])); + + opts.merge_tree_opts.automerge_flags = 0; + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 9)); + + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} + +void test_merge_workdir_simple__unrelated_with_conflicts(void) +{ + git_oid their_oids[1]; + git_merge_head *their_heads[1]; + git_merge_result *result; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, + { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &their_oids[0])); + + opts.merge_tree_opts.automerge_flags = 0; + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 11)); + + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); +} + diff --git a/tests/merge/workdir/trivial.c b/tests/merge/workdir/trivial.c new file mode 100644 index 000000000..9f9566243 --- /dev/null +++ b/tests/merge/workdir/trivial.c @@ -0,0 +1,341 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "git2/sys/index.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "refs.h" +#include "fileops.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + + +// Fixture setup and teardown +void test_merge_workdir_trivial__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_trivial__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + + +static int merge_trivial(const char *ours, const char *theirs, bool automerge) +{ + git_buf branch_buf = GIT_BUF_INIT; + git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT; + git_reference *our_ref, *their_ref; + git_merge_head *their_heads[1]; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + git_merge_result *result; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + opts.merge_tree_opts.automerge_flags |= automerge ? 0 : GIT_MERGE_AUTOMERGE_NONE; + + git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours); + cl_git_pass(git_reference_symbolic_create(&our_ref, repo, "HEAD", branch_buf.ptr, 1)); + + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + git_buf_clear(&branch_buf); + git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs); + cl_git_pass(git_reference_lookup(&their_ref, repo, branch_buf.ptr)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, their_ref)); + + cl_git_pass(git_merge(&result, repo, (const git_merge_head **)their_heads, 1, &opts)); + + git_buf_free(&branch_buf); + git_reference_free(our_ref); + git_reference_free(their_ref); + git_merge_head_free(their_heads[0]); + git_merge_result_free(result); + + return 0; +} + +static size_t merge_trivial_conflict_entrycount(void) +{ + const git_index_entry *entry; + size_t count = 0; + size_t i; + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + + if (git_index_entry_stage(entry) > 0) + count++; + } + + return count; +} + +/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */ +void test_merge_workdir_trivial__2alt(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-2alt", "trivial-2alt-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-in-branch.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */ +void test_merge_workdir_trivial__3alt(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-3alt", "trivial-3alt-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-in-3alt.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */ +void test_merge_workdir_trivial__4(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-4", "trivial-4-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "new-and-different.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 2)); + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 3)); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_workdir_trivial__5alt_1(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-5alt-1", "trivial-5alt-1-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_workdir_trivial__5alt_2(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-5alt-2", "trivial-5alt-2-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-to-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__6(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-6", "trivial-6-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 1); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 1)); +} + +/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__6_automerge(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-6", "trivial-6-branch", 1)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-both.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ +void test_merge_workdir_trivial__8(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-8", "trivial-8-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 3)); +} + +/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ +void test_merge_workdir_trivial__8_automerge(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-8", "trivial-8-branch", 1)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-8.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ +void test_merge_workdir_trivial__7(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-7", "trivial-7-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 3)); +} + +/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ +void test_merge_workdir_trivial__7_automerge(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-7", "trivial-7-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 3)); +} + +/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__10(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-10", "trivial-10-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 2)); +} + +/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__10_automerge(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-10", "trivial-10-branch", 1)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-10-branch.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__9(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-9", "trivial-9-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 2)); +} + +/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__9_automerge(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-9", "trivial-9-branch", 1)); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 2)); +} + +/* 13: ancest:ancest+, head:head, remote:ancest = result:head */ +void test_merge_workdir_trivial__13(void) +{ + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial("trivial-13", "trivial-13-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-13.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b")); + cl_assert(git_oid_cmp(&entry->oid, &expected_oid) == 0); + + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ +void test_merge_workdir_trivial__14(void) +{ + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial("trivial-14", "trivial-14-branch", 0)); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-14-branch.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9")); + cl_assert(git_oid_cmp(&entry->oid, &expected_oid) == 0); + + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */ +void test_merge_workdir_trivial__11(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-11", "trivial-11-branch", 0)); + + cl_assert((entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 3); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 2)); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 3)); +} diff --git a/tests-clar/network/cred.c b/tests/network/cred.c similarity index 100% rename from tests-clar/network/cred.c rename to tests/network/cred.c diff --git a/tests-clar/network/fetchlocal.c b/tests/network/fetchlocal.c similarity index 80% rename from tests-clar/network/fetchlocal.c rename to tests/network/fetchlocal.c index 09335b3df..28c7115bf 100644 --- a/tests-clar/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -25,13 +25,18 @@ void test_network_fetchlocal__complete(void) git_strarray refnames = {0}; const char *url = cl_git_fixture_url("testrepo.git"); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; cl_set_cleanup(&cleanup_local_repo, "foo"); cl_git_pass(git_repository_init(&repo, "foo", true)); cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, transfer_cb, &callcount)); + cl_git_pass(git_remote_download(origin)); cl_git_pass(git_remote_update_tips(origin)); cl_git_pass(git_reference_list(&refnames, repo)); @@ -56,6 +61,10 @@ void test_network_fetchlocal__partial(void) int callcount = 0; git_strarray refnames = {0}; const char *url; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; cl_set_cleanup(&cleanup_sandbox, NULL); cl_git_pass(git_reference_list(&refnames, repo)); @@ -63,8 +72,9 @@ void test_network_fetchlocal__partial(void) url = cl_git_fixture_url("testrepo.git"); cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, transfer_cb, &callcount)); + cl_git_pass(git_remote_download(origin)); cl_git_pass(git_remote_update_tips(origin)); git_strarray_free(&refnames); diff --git a/tests-clar/network/refspecs.c b/tests/network/refspecs.c similarity index 100% rename from tests-clar/network/refspecs.c rename to tests/network/refspecs.c diff --git a/tests-clar/network/remote/createthenload.c b/tests/network/remote/createthenload.c similarity index 100% rename from tests-clar/network/remote/createthenload.c rename to tests/network/remote/createthenload.c diff --git a/tests-clar/network/remote/isvalidname.c b/tests/network/remote/isvalidname.c similarity index 100% rename from tests-clar/network/remote/isvalidname.c rename to tests/network/remote/isvalidname.c diff --git a/tests-clar/network/remote/local.c b/tests/network/remote/local.c similarity index 50% rename from tests-clar/network/remote/local.c rename to tests/network/remote/local.c index 3cb8a25d6..309142925 100644 --- a/tests-clar/network/remote/local.c +++ b/tests/network/remote/local.c @@ -26,33 +26,12 @@ void test_network_remote_local__cleanup(void) cl_fixture_cleanup("remotelocal"); } -static int count_ref__cb(git_remote_head *head, void *payload) -{ - int *count = (int *)payload; - - (void)head; - (*count)++; - - return 0; -} - -static int ensure_peeled__cb(git_remote_head *head, void *payload) -{ - GIT_UNUSED(payload); - - if(strcmp(head->name, "refs/tags/test^{}") != 0) - return 0; - - return git_oid_streq(&head->oid, "e90810b8df3e80c413d903f631643c716887138d"); -} - 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_inmemory(&remote, repo, NULL, git_buf_cstr(&file_path_buf))); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - } void test_network_remote_local__connected(void) @@ -66,27 +45,42 @@ void test_network_remote_local__connected(void) void test_network_remote_local__retrieve_advertised_references(void) { - int how_many_refs = 0; + const git_remote_head **refs; + size_t refs_len; connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(how_many_refs, 28); + cl_assert_equal_i(refs_len, 28); +} + +void test_network_remote_local__retrieve_advertised_references_after_disconnect(void) +{ + const git_remote_head **refs; + size_t refs_len; + + connect_to_local_repository(cl_fixture("testrepo.git")); + git_remote_disconnect(remote); + + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_i(refs_len, 28); } void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) { - int how_many_refs = 0; + const git_remote_head **refs; + size_t refs_len; cl_fixture_sandbox("testrepo.git"); cl_git_pass(p_rename("testrepo.git", "spaced testrepo.git")); connect_to_local_repository("spaced testrepo.git"); - cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(how_many_refs, 28); + cl_assert_equal_i(refs_len, 28); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; @@ -96,9 +90,17 @@ void test_network_remote_local__retrieve_advertised_references_from_spaced_repos void test_network_remote_local__nested_tags_are_completely_peeled(void) { + const git_remote_head **refs; + size_t refs_len, i; + connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_ls(remote, &ensure_peeled__cb, NULL)); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + for (i = 0; i < refs_len; i++) { + if (!strcmp(refs[i]->name, "refs/tags/test^{}")) + cl_git_pass(git_oid_streq(&refs[i]->oid, "e90810b8df3e80c413d903f631643c716887138d")); + } } void test_network_remote_local__shorthand_fetch_refspec0(void) @@ -112,7 +114,7 @@ void test_network_remote_local__shorthand_fetch_refspec0(void) cl_git_pass(git_remote_add_fetch(remote, refspec)); cl_git_pass(git_remote_add_fetch(remote, refspec2)); - cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_download(remote)); cl_git_pass(git_remote_update_tips(remote)); cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); @@ -134,7 +136,7 @@ void test_network_remote_local__shorthand_fetch_refspec1(void) cl_git_pass(git_remote_add_fetch(remote, refspec)); cl_git_pass(git_remote_add_fetch(remote, refspec2)); - cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_download(remote)); cl_git_pass(git_remote_update_tips(remote)); cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/master")); @@ -149,7 +151,7 @@ void test_network_remote_local__tagopt(void) connect_to_local_repository(cl_fixture("testrepo.git")); git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_ALL); - cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_download(remote)); cl_git_pass(git_remote_update_tips(remote)); @@ -158,3 +160,75 @@ void test_network_remote_local__tagopt(void) cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); git_reference_free(ref); } + +void test_network_remote_local__push_to_bare_remote(void) +{ + /* Should be able to push to a bare remote */ + git_remote *localremote; + git_push *push; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_add_fetch(remote, "master:master")); + cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_update_tips(remote)); + git_remote_disconnect(remote); + + /* Set up an empty bare repo to push into */ + { + git_repository *localbarerepo; + cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); + git_repository_free(localbarerepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_inmemory(&localremote, repo, NULL, "./localbare.git")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH)); + + /* Try to push */ + cl_git_pass(git_push_new(&push, localremote)); + cl_git_pass(git_push_add_refspec(push, "refs/heads/master:")); + cl_git_pass(git_push_finish(push)); + cl_assert(git_push_unpack_ok(push)); + + /* Clean up */ + git_push_free(push); + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} + +void test_network_remote_local__push_to_non_bare_remote(void) +{ + /* Shouldn't be able to push to a non-bare remote */ + git_remote *localremote; + git_push *push; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_add_fetch(remote, "master:master")); + cl_git_pass(git_remote_download(remote)); + cl_git_pass(git_remote_update_tips(remote)); + git_remote_disconnect(remote); + + /* Set up an empty non-bare repo to push into */ + { + git_repository *remoterepo = NULL; + cl_git_pass(git_repository_init(&remoterepo, "localnonbare", 0)); + git_repository_free(remoterepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_inmemory(&localremote, repo, NULL, "./localnonbare")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH)); + + /* Try to push */ + cl_git_pass(git_push_new(&push, localremote)); + cl_git_pass(git_push_add_refspec(push, "refs/heads/master:")); + cl_git_fail_with(git_push_finish(push), GIT_EBAREREPO); + cl_assert_equal_i(0, git_push_unpack_ok(push)); + + /* Clean up */ + git_push_free(push); + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} diff --git a/tests-clar/network/remote/remotes.c b/tests/network/remote/remotes.c similarity index 85% rename from tests-clar/network/remote/remotes.c rename to tests/network/remote/remotes.c index 3c4fa96fa..954ded82c 100644 --- a/tests-clar/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -150,8 +150,10 @@ void test_network_remote_remotes__add_pushspec(void) void test_network_remote_remotes__save(void) { git_strarray array; - const char *fetch_refspec = "refs/heads/*:refs/remotes/upstream/*"; - const char *push_refspec = "refs/heads/*:refs/heads/*"; + const char *fetch_refspec1 = "refs/heads/ns1/*:refs/remotes/upstream/ns1/*"; + const char *fetch_refspec2 = "refs/heads/ns2/*:refs/remotes/upstream/ns2/*"; + const char *push_refspec1 = "refs/heads/ns1/*:refs/heads/ns1/*"; + const char *push_refspec2 = "refs/heads/ns2/*:refs/heads/ns2/*"; git_remote_free(_remote); _remote = NULL; @@ -160,8 +162,10 @@ void test_network_remote_remotes__save(void) cl_git_pass(git_remote_create(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2")); git_remote_clear_refspecs(_remote); - cl_git_pass(git_remote_add_fetch(_remote, fetch_refspec)); - cl_git_pass(git_remote_add_push(_remote, push_refspec)); + cl_git_pass(git_remote_add_fetch(_remote, fetch_refspec1)); + cl_git_pass(git_remote_add_fetch(_remote, fetch_refspec2)); + cl_git_pass(git_remote_add_push(_remote, push_refspec1)); + cl_git_pass(git_remote_add_push(_remote, push_refspec2)); cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push")); cl_git_pass(git_remote_save(_remote)); git_remote_free(_remote); @@ -171,16 +175,19 @@ void test_network_remote_remotes__save(void) cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); cl_git_pass(git_remote_get_fetch_refspecs(&array, _remote)); - cl_assert_equal_i(1, (int)array.count); - cl_assert_equal_s(fetch_refspec, array.strings[0]); + cl_assert_equal_i(2, (int)array.count); + cl_assert_equal_s(fetch_refspec1, array.strings[0]); + cl_assert_equal_s(fetch_refspec2, array.strings[1]); git_strarray_free(&array); cl_git_pass(git_remote_get_push_refspecs(&array, _remote)); - cl_assert_equal_i(1, (int)array.count); - cl_assert_equal_s(push_refspec, array.strings[0]); + cl_assert_equal_i(2, (int)array.count); + cl_assert_equal_s(push_refspec1, array.strings[0]); + cl_assert_equal_s(push_refspec2, array.strings[1]); + git_strarray_free(&array); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push"); - git_strarray_free(&array); /* remove the pushurl again and see if we can save that too */ cl_git_pass(git_remote_set_pushurl(_remote, NULL)); @@ -243,13 +250,19 @@ void test_network_remote_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 4); + cl_assert(list.count == 5); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); + + /* Create a new remote */ cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + + /* Update a remote (previously without any url/pushurl entry) */ + cl_git_pass(git_config_set_string(cfg, "remote.no-remote-url.pushurl", "http://example.com")); + cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 5); + cl_assert(list.count == 7); git_strarray_free(&list); git_config_free(cfg); @@ -361,13 +374,43 @@ void test_network_remote_remotes__tagopt(void) git_config_free(cfg); } -void test_network_remote_remotes__cannot_load_with_an_empty_url(void) +void test_network_remote_remotes__can_load_with_an_empty_url(void) { git_remote *remote = NULL; - cl_git_fail(git_remote_load(&remote, _repo, "empty-remote-url")); + cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-url")); + + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + + cl_assert(giterr_last() != NULL); cl_assert(giterr_last()->klass == GITERR_INVALID); - cl_assert_equal_p(remote, NULL); + + git_remote_free(remote); +} + +void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-pushurl")); + + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + + git_remote_free(remote); +} + +void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with( + git_remote_load(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND); } void test_network_remote_remotes__check_structure_version(void) @@ -407,7 +450,6 @@ void test_network_remote_remotes__cannot_create_a_remote_which_name_conflicts_wi assert_cannot_create_remote("test", GIT_EEXISTS); } - void test_network_remote_remotes__cannot_create_a_remote_which_name_is_invalid(void) { assert_cannot_create_remote("/", GIT_EINVALIDSPEC); @@ -416,6 +458,17 @@ void test_network_remote_remotes__cannot_create_a_remote_which_name_is_invalid(v assert_cannot_create_remote("a.lock", GIT_EINVALIDSPEC); } +void test_network_remote_remote__git_remote_create_with_fetchspec(void) +{ + git_remote *remote; + git_strarray array; + + cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", "+refs/*:refs/*")); + git_remote_get_fetch_refspecs(&array, remote); + cl_assert_equal_s("+refs/*:refs/*", array.strings[0]); + git_remote_free(remote); +} + static const char *fetch_refspecs[] = { "+refs/heads/*:refs/remotes/origin/*", "refs/tags/*:refs/tags/*", diff --git a/tests-clar/network/remote/rename.c b/tests/network/remote/rename.c similarity index 100% rename from tests-clar/network/remote/rename.c rename to tests/network/remote/rename.c diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c new file mode 100644 index 000000000..2a9c2f69f --- /dev/null +++ b/tests/network/urlparse.c @@ -0,0 +1,193 @@ +#include "clar_libgit2.h" +#include "netops.h" + +static char *host, *port, *path, *user, *pass; +static gitno_connection_data conndata; + +void test_network_urlparse__initialize(void) +{ + host = port = path = user = pass = NULL; + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_urlparse__cleanup(void) +{ +#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; } + FREE_AND_NULL(host); + FREE_AND_NULL(port); + FREE_AND_NULL(path); + FREE_AND_NULL(user); + FREE_AND_NULL(pass); + + gitno_connection_data_free_ptrs(&conndata); +} + +void test_network_urlparse__trivial(void) +{ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "http://example.com/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "8080"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_p(user, NULL); + cl_assert_equal_p(pass, NULL); +} + +void test_network_urlparse__encoded_password(void) +{ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user:pass%2fis%40bad@hostname.com:1234/", "1")); + cl_assert_equal_s(host, "hostname.com"); + cl_assert_equal_s(port, "1234"); + cl_assert_equal_s(path, "/"); + cl_assert_equal_s(user, "user"); + cl_assert_equal_s(pass, "pass/is@bad"); +} + +void test_network_urlparse__user(void) +{ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user@example.com/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "8080"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_s(user, "user"); + cl_assert_equal_p(pass, NULL); +} + +void test_network_urlparse__user_pass(void) +{ + /* user:pass@hostname.tld/resource */ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user:pass@example.com/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "8080"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_s(user, "user"); + cl_assert_equal_s(pass, "pass"); +} + +void test_network_urlparse__port(void) +{ + /* hostname.tld:port/resource */ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://example.com:9191/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "9191"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_p(user, NULL); + cl_assert_equal_p(pass, NULL); +} + +void test_network_urlparse__user_port(void) +{ + /* user@hostname.tld:port/resource */ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user@example.com:9191/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "9191"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_s(user, "user"); + cl_assert_equal_p(pass, NULL); +} + +void test_network_urlparse__user_pass_port(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user:pass@example.com:9191/resource", "8080")); + cl_assert_equal_s(host, "example.com"); + cl_assert_equal_s(port, "9191"); + cl_assert_equal_s(path, "/resource"); + cl_assert_equal_s(user, "user"); + cl_assert_equal_s(pass, "pass"); +} + +void test_network_urlparse__connection_data_http(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "http://example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.user, NULL); + cl_assert_equal_p(conndata.pass, NULL); + cl_assert_equal_i(conndata.use_ssl, false); +} + +void test_network_urlparse__connection_data_ssl(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "https://example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.user, NULL); + cl_assert_equal_p(conndata.pass, NULL); + cl_assert_equal_i(conndata.use_ssl, true); +} + +void test_network_urlparse__encoded_username_password(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_s(conndata.user, "user/name"); + cl_assert_equal_s(conndata.pass, "pass@word%zyx%v"); + cl_assert_equal_i(conndata.use_ssl, true); +} + +void test_network_urlparse__connection_data_cross_host_redirect(void) +{ + conndata.host = git__strdup("bar.com"); + cl_git_fail_with(gitno_connection_data_from_url(&conndata, + "https://foo.com/bar/baz", NULL), + -1); +} + +void test_network_urlparse__connection_data_http_downgrade(void) +{ + conndata.use_ssl = true; + cl_git_fail_with(gitno_connection_data_from_url(&conndata, + "http://foo.com/bar/baz", NULL), + -1); +} + +void test_network_urlparse__connection_data_relative_redirect(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "http://foo.com/bar/baz/biff", NULL)); + cl_git_pass(gitno_connection_data_from_url(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.user, NULL); + cl_assert_equal_p(conndata.pass, NULL); + cl_assert_equal_i(conndata.use_ssl, false); +} + +void test_network_urlparse__connection_data_relative_redirect_ssl(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "https://foo.com/bar/baz/biff", NULL)); + cl_git_pass(gitno_connection_data_from_url(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.user, NULL); + cl_assert_equal_p(conndata.pass, NULL); + cl_assert_equal_i(conndata.use_ssl, true); +} + +/* Run this under valgrind */ +void test_network_urlparse__connection_data_cleanup(void) +{ + cl_git_pass(gitno_connection_data_from_url(&conndata, + "http://foo.com/bar/baz/biff", "baz/biff")); + cl_git_pass(gitno_connection_data_from_url(&conndata, + "https://foo.com/bar/baz/biff", "baz/biff")); +} diff --git a/tests-clar/notes/notes.c b/tests/notes/notes.c similarity index 100% rename from tests-clar/notes/notes.c rename to tests/notes/notes.c diff --git a/tests-clar/notes/notesref.c b/tests/notes/notesref.c similarity index 100% rename from tests-clar/notes/notesref.c rename to tests/notes/notesref.c diff --git a/tests/object/blob/filter.c b/tests/object/blob/filter.c new file mode 100644 index 000000000..0b2d6bf9e --- /dev/null +++ b/tests/object/blob/filter.c @@ -0,0 +1,143 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "buf_text.h" + +static git_repository *g_repo = NULL; + +#define CRLF_NUM_TEST_OBJECTS 9 + +static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = { + "", + "foo\nbar\n", + "foo\rbar\r", + "foo\r\nbar\r\n", + "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", + "123\n\000\001\002\003\004abc\255\254\253\r\n", + "\xEF\xBB\xBFThis is UTF-8\n", + "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n", + "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" +}; + +static git_off_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { + -1, -1, -1, -1, -1, 17, -1, -1, 12 +}; + +static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS]; + +static git_buf g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = { + { "", 0, 0 }, + { "foo\nbar\n", 0, 8 }, + { "foo\rbar\r", 0, 8 }, + { "foo\nbar\n", 0, 8 }, + { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, + { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, + { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, + { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n", 0, 29 }, + { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } +}; + +static git_buf_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = { + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 2, 0, 6, 0 }, + { 0, 0, 2, 0, 0, 6, 0 }, + { 0, 0, 2, 2, 2, 6, 0 }, + { 0, 0, 4, 4, 1, 31, 0 }, + { 0, 1, 1, 2, 1, 9, 5 }, + { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, + { GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, + { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, +}; + +void test_object_blob_filter__initialize(void) +{ + int i; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + if (g_crlf_raw_len[i] < 0) + g_crlf_raw_len[i] = strlen(g_crlf_raw[i]); + + cl_git_pass(git_blob_create_frombuffer( + &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i])); + } +} + +void test_object_blob_filter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_blob_filter__unfiltered(void) +{ + int i; + git_blob *blob; + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + size_t raw_len = (size_t)g_crlf_raw_len[i]; + + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + + cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob)); + cl_assert_equal_i( + 0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len)); + + git_blob_free(blob); + } +} + +void test_object_blob_filter__stats(void) +{ + int i; + git_blob *blob; + git_buf buf = GIT_BUF_INIT; + git_buf_text_stats stats; + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + cl_git_pass(git_blob__getbuf(&buf, blob)); + git_buf_text_gather_stats(&stats, &buf, false); + cl_assert_equal_i( + 0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats))); + git_blob_free(blob); + } + + git_buf_free(&buf); +} + +void test_object_blob_filter__to_odb(void) +{ + git_filter_list *fl = NULL; + git_config *cfg; + int i; + git_blob *blob; + git_buf out = GIT_BUF_INIT; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_assert(cfg); + + git_attr_cache_flush(g_repo); + cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB)); + cl_assert(fl != NULL); + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + + cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); + + cl_assert_equal_sz(g_crlf_filtered[i].size, out.size); + + cl_assert_equal_i( + 0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size)); + + git_blob_free(blob); + } + + git_filter_list_free(fl); + git_buf_free(&out); + git_config_free(cfg); +} diff --git a/tests-clar/object/blob/fromchunks.c b/tests/object/blob/fromchunks.c similarity index 64% rename from tests-clar/object/blob/fromchunks.c rename to tests/object/blob/fromchunks.c index dc57d4fbe..03ed4efb4 100644 --- a/tests-clar/object/blob/fromchunks.c +++ b/tests/object/blob/fromchunks.c @@ -41,14 +41,46 @@ void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provi cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - cl_git_fail(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + cl_git_fail_with( + git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), + GIT_ENOTFOUND); cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + cl_assert(git_oid_cmp(&expected_oid, git_object_id(blob)) == 0); + git_object_free(blob); } +void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void) +{ + git_buf path = GIT_BUF_INIT; + git_buf content = GIT_BUF_INIT; + git_oid expected_oid, oid; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); + + /* Let's replace the content of the blob file storage with something else... */ + cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9")); + cl_git_pass(p_unlink(git_buf_cstr(&path))); + cl_git_mkfile(git_buf_cstr(&path), "boom"); + + /* ...request a creation of the same blob... */ + howmany = 7; + cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); + + /* ...and ensure the content of the faked blob file hasn't been altered */ + cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path))); + cl_assert(!git__strcmp("boom", git_buf_cstr(&content))); + + git_buf_free(&path); + git_buf_free(&content); +} + #define GITATTR "* text=auto\n" \ "*.txt text\n" \ "*.data binary\n" diff --git a/tests-clar/object/blob/write.c b/tests/object/blob/write.c similarity index 100% rename from tests-clar/object/blob/write.c rename to tests/object/blob/write.c diff --git a/tests-clar/object/cache.c b/tests/object/cache.c similarity index 100% rename from tests-clar/object/cache.c rename to tests/object/cache.c diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests/object/commit/commitstagedfile.c similarity index 100% rename from tests-clar/object/commit/commitstagedfile.c rename to tests/object/commit/commitstagedfile.c diff --git a/tests-clar/object/lookup.c b/tests/object/lookup.c similarity index 100% rename from tests-clar/object/lookup.c rename to tests/object/lookup.c diff --git a/tests/object/lookupbypath.c b/tests/object/lookupbypath.c new file mode 100644 index 000000000..31aac7647 --- /dev/null +++ b/tests/object/lookupbypath.c @@ -0,0 +1,83 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static git_repository *g_repo; +static git_tree *g_root_tree; +static git_commit *g_head_commit; +static git_object *g_expectedobject, + *g_actualobject; + +void test_object_lookupbypath__initialize(void) +{ + git_reference *head; + git_tree_entry *tree_entry; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted"))); + + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJ_COMMIT)); + cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit)); + cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt")); + cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry), + GIT_OBJ_ANY)); + + git_tree_entry_free(tree_entry); + git_reference_free(head); + + g_actualobject = NULL; +} +void test_object_lookupbypath__cleanup(void) +{ + git_object_free(g_actualobject); + git_object_free(g_expectedobject); + git_tree_free(g_root_tree); + git_commit_free(g_head_commit); + g_expectedobject = NULL; + git_repository_free(g_repo); + g_repo = NULL; +} + +void test_object_lookupbypath__errors(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "subdir/subdir_test2.txt", GIT_OBJ_TREE)); // It's not a tree + cl_assert_equal_i(GIT_ENOTFOUND, + git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "file/doesnt/exist", GIT_OBJ_ANY)); +} + +void test_object_lookupbypath__from_root_tree(void) +{ + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "subdir/subdir_test2.txt", GIT_OBJ_BLOB)); + cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject), + git_object_id(g_actualobject))); +} + +void test_object_lookupbypath__from_head_commit(void) +{ + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit, + "subdir/subdir_test2.txt", GIT_OBJ_BLOB)); + cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject), + git_object_id(g_actualobject))); +} + +void test_object_lookupbypath__from_subdir_tree(void) +{ + git_tree_entry *entry = NULL; + git_tree *tree = NULL; + + cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir")); + cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry))); + + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree, + "subdir_test2.txt", GIT_OBJ_BLOB)); + cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject), + git_object_id(g_actualobject))); + + git_tree_entry_free(entry); + git_tree_free(tree); +} + diff --git a/tests-clar/object/message.c b/tests/object/message.c similarity index 100% rename from tests-clar/object/message.c rename to tests/object/message.c diff --git a/tests-clar/object/peel.c b/tests/object/peel.c similarity index 100% rename from tests-clar/object/peel.c rename to tests/object/peel.c diff --git a/tests-clar/object/raw/chars.c b/tests/object/raw/chars.c similarity index 100% rename from tests-clar/object/raw/chars.c rename to tests/object/raw/chars.c diff --git a/tests-clar/object/raw/compare.c b/tests/object/raw/compare.c similarity index 100% rename from tests-clar/object/raw/compare.c rename to tests/object/raw/compare.c diff --git a/tests-clar/object/raw/convert.c b/tests/object/raw/convert.c similarity index 100% rename from tests-clar/object/raw/convert.c rename to tests/object/raw/convert.c diff --git a/tests-clar/object/raw/data.h b/tests/object/raw/data.h similarity index 100% rename from tests-clar/object/raw/data.h rename to tests/object/raw/data.h diff --git a/tests-clar/object/raw/fromstr.c b/tests/object/raw/fromstr.c similarity index 100% rename from tests-clar/object/raw/fromstr.c rename to tests/object/raw/fromstr.c diff --git a/tests-clar/object/raw/hash.c b/tests/object/raw/hash.c similarity index 100% rename from tests-clar/object/raw/hash.c rename to tests/object/raw/hash.c diff --git a/tests-clar/object/raw/short.c b/tests/object/raw/short.c similarity index 100% rename from tests-clar/object/raw/short.c rename to tests/object/raw/short.c diff --git a/tests-clar/object/raw/size.c b/tests/object/raw/size.c similarity index 100% rename from tests-clar/object/raw/size.c rename to tests/object/raw/size.c diff --git a/tests-clar/object/raw/type2string.c b/tests/object/raw/type2string.c similarity index 100% rename from tests-clar/object/raw/type2string.c rename to tests/object/raw/type2string.c diff --git a/tests-clar/object/raw/write.c b/tests/object/raw/write.c similarity index 99% rename from tests-clar/object/raw/write.c rename to tests/object/raw/write.c index 9709c0302..273f08f2c 100644 --- a/tests-clar/object/raw/write.c +++ b/tests/object/raw/write.c @@ -31,9 +31,9 @@ static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) int error; cl_git_pass(git_odb_open_wstream(&stream, odb, raw->len, raw->type)); - stream->write(stream, raw->data, raw->len); - error = stream->finalize_write(oid, stream); - stream->free(stream); + git_odb_stream_write(stream, raw->data, raw->len); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); cl_git_pass(error); } diff --git a/tests-clar/object/tag/list.c b/tests/object/tag/list.c similarity index 100% rename from tests-clar/object/tag/list.c rename to tests/object/tag/list.c diff --git a/tests-clar/object/tag/peel.c b/tests/object/tag/peel.c similarity index 100% rename from tests-clar/object/tag/peel.c rename to tests/object/tag/peel.c diff --git a/tests-clar/object/tag/read.c b/tests/object/tag/read.c similarity index 100% rename from tests-clar/object/tag/read.c rename to tests/object/tag/read.c diff --git a/tests-clar/object/tag/write.c b/tests/object/tag/write.c similarity index 100% rename from tests-clar/object/tag/write.c rename to tests/object/tag/write.c diff --git a/tests-clar/object/tree/attributes.c b/tests/object/tree/attributes.c similarity index 95% rename from tests-clar/object/tree/attributes.c rename to tests/object/tree/attributes.c index cc93b45d2..85216cd1b 100644 --- a/tests-clar/object/tree/attributes.c +++ b/tests/object/tree/attributes.c @@ -107,7 +107,8 @@ void test_object_tree_attributes__normalize_600(void) cl_git_pass(git_tree_lookup(&tree, repo, &id)); entry = git_tree_entry_byname(tree, "ListaTeste.xml"); - cl_assert_equal_i(entry->attr, GIT_FILEMODE_BLOB); + cl_assert_equal_i(git_tree_entry_filemode(entry), GIT_FILEMODE_BLOB); + cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600); git_tree_free(tree); cl_git_sandbox_cleanup(); diff --git a/tests-clar/object/tree/duplicateentries.c b/tests/object/tree/duplicateentries.c similarity index 100% rename from tests-clar/object/tree/duplicateentries.c rename to tests/object/tree/duplicateentries.c diff --git a/tests-clar/object/tree/frompath.c b/tests/object/tree/frompath.c similarity index 100% rename from tests-clar/object/tree/frompath.c rename to tests/object/tree/frompath.c diff --git a/tests-clar/object/tree/read.c b/tests/object/tree/read.c similarity index 100% rename from tests-clar/object/tree/read.c rename to tests/object/tree/read.c diff --git a/tests-clar/object/tree/walk.c b/tests/object/tree/walk.c similarity index 54% rename from tests-clar/object/tree/walk.c rename to tests/object/tree/walk.c index b7af4924d..1207e864c 100644 --- a/tests-clar/object/tree/walk.c +++ b/tests/object/tree/walk.c @@ -101,3 +101,77 @@ void test_object_tree_walk__1(void) git_tree_free(tree); } + + +struct treewalk_skip_data { + int files; + int dirs; + const char *skip; + const char *stop; +}; + +static int treewalk_skip_de_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + struct treewalk_skip_data *data = payload; + const char *name = git_tree_entry_name(entry); + + GIT_UNUSED(root); + + if (git_tree_entry_type(entry) == GIT_OBJ_TREE) + data->dirs++; + else + data->files++; + + if (data->skip && !strcmp(name, data->skip)) + return 1; + else if (data->stop && !strcmp(name, data->stop)) + return -1; + else + return 0; +} + +void test_object_tree_walk__2(void) +{ + git_oid id; + git_tree *tree; + struct treewalk_skip_data data; + + /* look up a deep tree */ + git_oid_fromstr(&id, "ae90f12eea699729ed24555e40b9fd669da12a12"); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + memset(&data, 0, sizeof(data)); + data.skip = "de"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(5, data.files); + cl_assert_equal_i(3, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "3.txt"; + + cl_assert_equal_i(GIT_EUSER, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(3, data.files); + cl_assert_equal_i(2, data.dirs); + + memset(&data, 0, sizeof(data)); + data.skip = "new.txt"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "new.txt"; + + cl_assert_equal_i(GIT_EUSER, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + git_tree_free(tree); +} diff --git a/tests-clar/object/tree/write.c b/tests/object/tree/write.c similarity index 100% rename from tests-clar/object/tree/write.c rename to tests/object/tree/write.c diff --git a/tests-clar/odb/alternates.c b/tests/odb/alternates.c similarity index 95% rename from tests-clar/odb/alternates.c rename to tests/odb/alternates.c index 4e876c2b3..c75f6feaa 100644 --- a/tests-clar/odb/alternates.c +++ b/tests/odb/alternates.c @@ -32,9 +32,9 @@ static void init_linked_repo(const char *path, const char *alternate) cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 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)); + cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0, 0666)); git_filebuf_printf(&file, "%s\n", git_buf_cstr(&destpath)); - cl_git_pass(git_filebuf_commit(&file, 0644)); + cl_git_pass(git_filebuf_commit(&file)); git_repository_free(repo); } diff --git a/tests/odb/backend/nonrefreshing.c b/tests/odb/backend/nonrefreshing.c new file mode 100644 index 000000000..b43529479 --- /dev/null +++ b/tests/odb/backend/nonrefreshing.c @@ -0,0 +1,274 @@ +#include "clar_libgit2.h" +#include "git2/sys/odb_backend.h" +#include "repository.h" + +typedef struct fake_backend { + git_odb_backend parent; + + git_error_code error_code; + + int exists_calls; + int read_calls; + int read_header_calls; + int read_prefix_calls; +} fake_backend; + +static git_repository *_repo; +static fake_backend *_fake; +static git_oid _oid; + +static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->exists_calls++; + + return (fake->error_code == GIT_OK); +} + +static int fake_backend__read( + void **buffer_p, size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(buffer_p); + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->read_calls++; + + *len_p = 0; + *buffer_p = NULL; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static int fake_backend__read_header( + size_t *len_p, git_otype *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(oid); + + fake = (fake_backend *)backend; + + fake->read_header_calls++; + + *len_p = 0; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static int fake_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) +{ + fake_backend *fake; + + GIT_UNUSED(out_oid); + GIT_UNUSED(buffer_p); + GIT_UNUSED(len_p); + GIT_UNUSED(type_p); + GIT_UNUSED(short_oid); + GIT_UNUSED(len); + + fake = (fake_backend *)backend; + + fake->read_prefix_calls++; + + *len_p = 0; + *buffer_p = NULL; + *type_p = GIT_OBJ_BLOB; + + return fake->error_code; +} + +static void fake_backend__free(git_odb_backend *_backend) +{ + fake_backend *backend; + + backend = (fake_backend *)_backend; + + git__free(backend); +} + +static int build_fake_backend( + git_odb_backend **out, + git_error_code error_code) +{ + fake_backend *backend; + + backend = git__calloc(1, sizeof(fake_backend)); + GITERR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + + backend->parent.refresh = NULL; + backend->error_code = error_code; + + backend->parent.read = fake_backend__read; + backend->parent.read_prefix = fake_backend__read_prefix; + backend->parent.read_header = fake_backend__read_header; + backend->parent.exists = fake_backend__exists; + backend->parent.free = &fake_backend__free; + + *out = (git_odb_backend *)backend; + + return 0; +} + +static void setup_repository_and_backend(git_error_code error_code) +{ + git_odb *odb = NULL; + git_odb_backend *backend = NULL; + + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(build_fake_backend(&backend, error_code)); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, backend, 10)); + + _fake = (fake_backend *)backend; + + cl_git_pass(git_oid_fromstr(&_oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); +} + +void test_odb_backend_nonrefreshing__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_failure(void) +{ + git_odb *odb; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(false, git_odb_exists(odb, &_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_failure(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_failure(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_prefix_calls); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_failure(void) +{ + git_odb *odb; + size_t len; + git_otype type; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_fail_with( + git_odb_read_header(&len, &type, odb, &_oid), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_header_calls); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_success(void) +{ + git_odb *odb; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(true, git_odb_exists(odb, &_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_success(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY)); + + cl_assert_equal_i(1, _fake->read_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_success(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY)); + + cl_assert_equal_i(1, _fake->read_prefix_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void) +{ + git_odb *odb; + size_t len; + git_otype type; + + setup_repository_and_backend(GIT_OK); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_pass(git_odb_read_header(&len, &type, odb, &_oid)); + + cl_assert_equal_i(1, _fake->read_header_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_when_revparsing_a_full_oid(void) +{ + git_object *obj; + + setup_repository_and_backend(GIT_ENOTFOUND); + + cl_git_fail_with( + git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} diff --git a/tests-clar/odb/foreach.c b/tests/odb/foreach.c similarity index 95% rename from tests-clar/odb/foreach.c rename to tests/odb/foreach.c index f643d9621..ebb8866f7 100644 --- a/tests-clar/odb/foreach.c +++ b/tests/odb/foreach.c @@ -27,7 +27,7 @@ static int foreach_cb(const git_oid *oid, void *data) } /* - * $ git --git-dir tests-clar/resources/testrepo.git count-objects --verbose + * $ git --git-dir tests/resources/testrepo.git count-objects --verbose * count: 47 * size: 4 * in-pack: 1640 diff --git a/tests-clar/odb/loose.c b/tests/odb/loose.c similarity index 55% rename from tests-clar/odb/loose.c rename to tests/odb/loose.c index 9539bb24c..a85f1430d 100644 --- a/tests-clar/odb/loose.c +++ b/tests/odb/loose.c @@ -1,8 +1,14 @@ #include "clar_libgit2.h" #include "odb.h" +#include "git2/odb_backend.h" #include "posix.h" #include "loose_data.h" +#ifdef __ANDROID_API__ +# define S_IREAD S_IRUSR +# define S_IWRITE S_IWUSR +#endif + static void write_object_files(object_data *d) { int fd; @@ -87,3 +93,54 @@ void test_odb_loose__simple_reads(void) test_read_object(&two); test_read_object(&some); } + +void test_write_object_permission( + mode_t dir_mode, mode_t file_mode, + mode_t expected_dir_mode, mode_t expected_file_mode) +{ + git_odb *odb; + git_odb_backend *backend; + git_oid oid; + struct stat statbuf; + mode_t mask, os_mask; + + /* Windows does not return group/user bits from stat, + * files are never executable. + */ +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + mask = p_umask(0); + p_umask(mask); + + cl_git_pass(git_odb_new(&odb)); + cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, 0, dir_mode, file_mode)); + cl_git_pass(git_odb_add_backend(odb, backend, 1)); + cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJ_BLOB)); + + cl_git_pass(p_stat("test-objects/67", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_dir_mode & ~mask) & os_mask); + + cl_git_pass(p_stat("test-objects/67/b808feb36201507a77f85e6d898f0a2836e4a5", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_file_mode & ~mask) & os_mask); + + git_odb_free(odb); +} + +void test_odb_loose__permissions_standard(void) +{ + test_write_object_permission(0, 0, GIT_OBJECT_DIR_MODE, GIT_OBJECT_FILE_MODE); +} + +void test_odb_loose_permissions_readonly(void) +{ + test_write_object_permission(0777, 0444, 0777, 0444); +} + +void test_odb_loose__permissions_readwrite(void) +{ + test_write_object_permission(0777, 0666, 0777, 0666); +} diff --git a/tests-clar/odb/loose_data.h b/tests/odb/loose_data.h similarity index 100% rename from tests-clar/odb/loose_data.h rename to tests/odb/loose_data.h diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c new file mode 100644 index 000000000..51970ceec --- /dev/null +++ b/tests/odb/mixed.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "odb.h" + +static git_odb *_odb; + +void test_odb_mixed__initialize(void) +{ + cl_git_pass(git_odb_open(&_odb, cl_fixture("duplicate.git/objects"))); +} + +void test_odb_mixed__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; +} + +void test_odb_mixed__dup_oid(void) { + const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; + const char short_hex[] = "ce01362"; + git_oid oid; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&oid, hex)); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); + git_odb_object_free(obj); + cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1)); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1)); + git_odb_object_free(obj); +} + +/* some known sha collisions of file content: + * 'aabqhq' and 'aaazvc' with prefix 'dea509d0' (+ '9' and + 'b') + * 'aaeufo' and 'aaaohs' with prefix '81b5bff5' (+ 'f' and + 'b') + * 'aafewy' and 'aaepta' with prefix '739e3c4c' + * 'aahsyn' and 'aadrjg' with prefix '0ddeaded' (+ '9' and + 'e') + */ + +void test_odb_mixed__dup_oid_prefix_0(void) { + char hex[10]; + git_oid oid; + git_odb_object *obj; + + /* ambiguous in the same pack file */ + + strncpy(hex, "dea509d0", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "dea509d09", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "dea509d0b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in different pack files */ + + strncpy(hex, "81b5bff5", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "81b5bff5b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "81b5bff5f", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in pack file and loose */ + + strncpy(hex, "0ddeaded", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + + strncpy(hex, "0ddeaded9", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + strncpy(hex, "0ddeadede", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); +} diff --git a/tests-clar/odb/pack_data.h b/tests/odb/pack_data.h similarity index 100% rename from tests-clar/odb/pack_data.h rename to tests/odb/pack_data.h diff --git a/tests-clar/odb/pack_data_one.h b/tests/odb/pack_data_one.h similarity index 100% rename from tests-clar/odb/pack_data_one.h rename to tests/odb/pack_data_one.h diff --git a/tests-clar/odb/packed.c b/tests/odb/packed.c similarity index 100% rename from tests-clar/odb/packed.c rename to tests/odb/packed.c diff --git a/tests-clar/odb/packed_one.c b/tests/odb/packed_one.c similarity index 100% rename from tests-clar/odb/packed_one.c rename to tests/odb/packed_one.c diff --git a/tests-clar/odb/sorting.c b/tests/odb/sorting.c similarity index 100% rename from tests-clar/odb/sorting.c rename to tests/odb/sorting.c diff --git a/tests/odb/streamwrite.c b/tests/odb/streamwrite.c new file mode 100644 index 000000000..591a20040 --- /dev/null +++ b/tests/odb/streamwrite.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" + +static git_repository *repo; +static git_odb *odb; +static git_odb_stream *stream; + +void test_odb_streamwrite__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); + + cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJ_BLOB)); + cl_assert_equal_sz(14, stream->declared_size); +} + +void test_odb_streamwrite__cleanup(void) +{ + git_odb_stream_free(stream); + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +void test_odb_streamwrite__can_accept_chunks(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6)); + cl_assert_equal_sz(8 + 6, stream->received_bytes); + + cl_git_pass(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_missing_bytes(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4)); + cl_assert_equal_sz(8 + 4, stream->received_bytes); + + cl_git_fail(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_additional_bytes(void) +{ + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7)); +} diff --git a/tests-clar/online/clone.c b/tests/online/clone.c similarity index 62% rename from tests-clar/online/clone.c rename to tests/online/clone.c index bc4285a00..efc76d958 100644 --- a/tests-clar/online/clone.c +++ b/tests/online/clone.c @@ -11,6 +11,7 @@ #define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git" #define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git" #define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git" +#define ASSEMBLA_REPO_URL "https://libgit2:_Libgit2@git.assembla.com/libgit2-test-repos.git" static git_repository *g_repo; static git_clone_options g_options; @@ -18,6 +19,7 @@ static git_clone_options g_options; void test_online_clone__initialize(void) { git_checkout_opts dummy_opts = GIT_CHECKOUT_OPTS_INIT; + git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT; g_repo = NULL; @@ -25,6 +27,7 @@ void test_online_clone__initialize(void) g_options.version = GIT_CLONE_OPTIONS_VERSION; g_options.checkout_opts = dummy_opts; g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_options.remote_callbacks = dummy_callbacks; } void test_online_clone__cleanup(void) @@ -69,7 +72,7 @@ void test_online_clone__empty_repository(void) cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options)); cl_assert_equal_i(true, git_repository_is_empty(g_repo)); - cl_assert_equal_i(true, git_repository_head_orphan(g_repo)); + cl_assert_equal_i(true, git_repository_head_unborn(g_repo)); cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); @@ -103,8 +106,8 @@ void test_online_clone__can_checkout_a_cloned_repo(void) g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; g_options.checkout_opts.progress_cb = &checkout_progress; g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called; - g_options.fetch_progress_cb = &fetch_progress; - g_options.fetch_progress_payload = &fetch_progress_cb_was_called; + g_options.remote_callbacks.transfer_progress = &fetch_progress; + g_options.remote_callbacks.payload = &fetch_progress_cb_was_called; cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); @@ -122,6 +125,45 @@ void test_online_clone__can_checkout_a_cloned_repo(void) git_buf_free(&path); } +void test_online_clone__clone_into(void) +{ + git_buf path = GIT_BUF_INIT; + git_remote *remote; + git_reference *head; + git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + bool checkout_progress_cb_was_called = false, + fetch_progress_cb_was_called = false; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + checkout_opts.progress_cb = &checkout_progress; + checkout_opts.progress_payload = &checkout_progress_cb_was_called; + + cl_git_pass(git_repository_init(&g_repo, "./foo", false)); + cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL)); + + callbacks.transfer_progress = &fetch_progress; + callbacks.payload = &fetch_progress_cb_was_called; + git_remote_set_callbacks(remote, &callbacks); + + cl_git_pass(git_clone_into(g_repo, remote, &checkout_opts, NULL)); + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path))); + + cl_git_pass(git_reference_lookup(&head, g_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_assert_equal_i(true, checkout_progress_cb_was_called); + cl_assert_equal_i(true, fetch_progress_cb_was_called); + + git_remote_free(remote); + git_reference_free(head); + git_buf_free(&path); +} + static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) { int *callcount = (int*)payload; @@ -132,17 +174,50 @@ static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void test_online_clone__custom_remote_callbacks(void) { - git_remote_callbacks remote_callbacks = GIT_REMOTE_CALLBACKS_INIT; int callcount = 0; - g_options.remote_callbacks = &remote_callbacks; - remote_callbacks.update_tips = update_tips; - remote_callbacks.payload = &callcount; + g_options.remote_callbacks.update_tips = update_tips; + g_options.remote_callbacks.payload = &callcount; cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); cl_assert(callcount > 0); } +static int cred_failure_cb( + git_cred **cred, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *data) +{ + GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); + GIT_UNUSED(allowed_types); GIT_UNUSED(data); + return -1; +} + +void test_online_clone__cred_callback_failure_is_euser(void) +{ + const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); + const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); + const char *remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); + int error; + + if (!remote_url) { + printf("GITTEST_REMOTE_URL unset; skipping clone test\n"); + return; + } + + if (!remote_user && !remote_default) { + printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_DEFAULT unset; skipping clone test\n"); + return; + } + + g_options.remote_callbacks.credentials = cred_failure_cb; + + cl_git_fail(error = git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_assert_equal_i(error, GIT_EUSER); +} + void test_online_clone__credentials(void) { /* Remote URL environment variable must be set. User and password are optional. */ @@ -154,8 +229,8 @@ void test_online_clone__credentials(void) if (!remote_url) return; - g_options.cred_acquire_cb = git_cred_userpass; - g_options.cred_acquire_payload = &user_pass; + g_options.remote_callbacks.credentials = git_cred_userpass; + g_options.remote_callbacks.payload = &user_pass; cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; @@ -168,8 +243,8 @@ void test_online_clone__bitbucket_style(void) "libgit2", "libgit2" }; - g_options.cred_acquire_cb = git_cred_userpass; - g_options.cred_acquire_payload = &user_pass; + g_options.remote_callbacks.credentials = git_cred_userpass; + g_options.remote_callbacks.payload = &user_pass; cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; @@ -188,6 +263,11 @@ void test_online_clone__bitbucket_style(void) cl_fixture_cleanup("./foo"); } +void test_online_clone__assembla_style(void) +{ + cl_git_pass(git_clone(&g_repo, ASSEMBLA_REPO_URL, "./foo", NULL)); +} + static int cancel_at_half(const git_transfer_progress *stats, void *payload) { GIT_UNUSED(payload); @@ -199,6 +279,13 @@ static int cancel_at_half(const git_transfer_progress *stats, void *payload) void test_online_clone__can_cancel(void) { - g_options.fetch_progress_cb = cancel_at_half; + g_options.remote_callbacks.transfer_progress = cancel_at_half; + cl_git_fail_with(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), GIT_EUSER); } + + + + + + diff --git a/tests-clar/online/fetch.c b/tests/online/fetch.c similarity index 78% rename from tests-clar/online/fetch.c rename to tests/online/fetch.c index bfa1eb972..5153a7ae0 100644 --- a/tests-clar/online/fetch.c +++ b/tests/online/fetch.c @@ -38,14 +38,16 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; size_t bytes_received = 0; + callbacks.transfer_progress = progress; callbacks.update_tips = update_tips; + callbacks.payload = &bytes_received; counter = 0; cl_git_pass(git_remote_create(&remote, _repo, "test", url)); git_remote_set_callbacks(remote, &callbacks); git_remote_set_autotag(remote, flag); cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(remote, progress, &bytes_received)); + cl_git_pass(git_remote_download(remote)); cl_git_pass(git_remote_update_tips(remote)); git_remote_disconnect(remote); cl_assert_equal_i(counter, n); @@ -64,6 +66,11 @@ void test_online_fetch__default_http(void) do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); } +void test_online_fetch__default_https(void) +{ + do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); +} + void test_online_fetch__no_tags_git(void) { do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); @@ -88,6 +95,7 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date git_repository *_repository; bool invoked = false; git_remote *remote; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; opts.bare = true; @@ -102,7 +110,10 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date cl_assert_equal_i(false, invoked); - cl_git_pass(git_remote_download(remote, &transferProgressCallback, &invoked)); + callbacks.transfer_progress = &transferProgressCallback; + callbacks.payload = &invoked; + git_remote_set_callbacks(remote, &callbacks); + cl_git_pass(git_remote_download(remote)); cl_assert_equal_i(false, invoked); @@ -126,38 +137,35 @@ void test_online_fetch__can_cancel(void) { git_remote *remote; size_t bytes_received = 0; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); + + callbacks.transfer_progress = cancel_at_half; + callbacks.payload = &bytes_received; + git_remote_set_callbacks(remote, &callbacks); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_fail_with(git_remote_download(remote, cancel_at_half, &bytes_received), GIT_EUSER); + cl_git_fail_with(git_remote_download(remote), GIT_EUSER); git_remote_disconnect(remote); git_remote_free(remote); } -int ls_cb(git_remote_head *rhead, void *payload) -{ - int *nr = payload; - GIT_UNUSED(rhead); - - (*nr)++; - - return 0; -} - void test_online_fetch__ls_disconnected(void) { + const git_remote_head **refs; + size_t refs_len_before, refs_len_after; git_remote *remote; - int nr_before = 0, nr_after = 0; 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)); - cl_git_pass(git_remote_ls(remote, ls_cb, &nr_before)); + cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote)); git_remote_disconnect(remote); - cl_git_pass(git_remote_ls(remote, ls_cb, &nr_after)); + cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote)); - cl_assert_equal_i(nr_before, nr_after); + cl_assert_equal_i(refs_len_before, refs_len_after); git_remote_free(remote); } diff --git a/tests-clar/online/fetchhead.c b/tests/online/fetchhead.c similarity index 93% rename from tests-clar/online/fetchhead.c rename to tests/online/fetchhead.c index 58717eef8..57b183f88 100644 --- a/tests-clar/online/fetchhead.c +++ b/tests/online/fetchhead.c @@ -12,10 +12,12 @@ static git_clone_options g_options; void test_online_fetchhead__initialize(void) { + git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT; g_repo = NULL; memset(&g_options, 0, sizeof(git_clone_options)); g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.remote_callbacks = dummy_callbacks; } void test_online_fetchhead__cleanup(void) @@ -48,7 +50,7 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet } cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(remote, NULL, NULL)); + cl_git_pass(git_remote_download(remote)); cl_git_pass(git_remote_update_tips(remote)); git_remote_disconnect(remote); git_remote_free(remote); diff --git a/tests-clar/online/push.c b/tests/online/push.c similarity index 71% rename from tests-clar/online/push.c rename to tests/online/push.c index 5dc7974c7..be505c3a1 100644 --- a/tests-clar/online/push.c +++ b/tests/online/push.c @@ -10,11 +10,19 @@ static git_repository *_repo; static char *_remote_url; + +static char *_remote_ssh_key; +static char *_remote_ssh_pubkey; +static char *_remote_ssh_passphrase; + static char *_remote_user; static char *_remote_pass; +static char *_remote_default; + +static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *); + static git_remote *_remote; -static bool _cred_acquire_called; static record_callbacks_data _record_cbs_data = {{ 0 }}; static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data); @@ -29,28 +37,54 @@ static git_oid _tag_commit; static git_oid _tag_tree; static git_oid _tag_blob; static git_oid _tag_lightweight; +static git_oid _tag_tag; static int cred_acquire_cb( - git_cred **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) + git_cred **cred, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) { GIT_UNUSED(url); GIT_UNUSED(user_from_url); + GIT_UNUSED(payload); - *((bool*)payload) = true; + if (GIT_CREDTYPE_DEFAULT & allowed_types) { + if (!_remote_default) { + printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); + return -1; + } - if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 || - git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass) < 0) - return -1; + return git_cred_default_new(cred); + } - return 0; + if (GIT_CREDTYPE_SSH_KEY & allowed_types) { + if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) { + printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n"); + return -1; + } + + return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); + } + + if (GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) { + if (!_remote_user || !_remote_pass) { + printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n"); + return -1; + } + + return git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass); + } + + return -1; } +/* the results of a push status. when used for expected values, msg may be NULL + * to indicate that it should not be matched. */ typedef struct { const char *ref; + int success; const char *msg; } push_status; @@ -65,6 +99,7 @@ static int record_push_status_cb(const char *ref, const char *msg, void *data) cl_assert(s = git__malloc(sizeof(*s))); s->ref = ref; + s->success = (msg == NULL); s->msg = msg; git_vector_insert(statuses, s); @@ -86,9 +121,8 @@ static void do_verify_push_status(git_push *push, const push_status expected[], else git_vector_foreach(&actual, i, iter) if (strcmp(expected[i].ref, iter->ref) || - (expected[i].msg && !iter->msg) || - (!expected[i].msg && iter->msg) || - (expected[i].msg && iter->msg && strcmp(expected[i].msg, iter->msg))) { + (expected[i].success != iter->success) || + (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) { failed = true; break; } @@ -101,13 +135,17 @@ static void do_verify_push_status(git_push *push, const push_status expected[], for(i = 0; i < expected_len; i++) { git_buf_printf(&msg, "%s: %s\n", expected[i].ref, - expected[i].msg ? expected[i].msg : ""); + expected[i].success ? "success" : "failed"); } git_buf_puts(&msg, "\nACTUAL:\n"); - git_vector_foreach(&actual, i, iter) - git_buf_printf(&msg, "%s: %s\n", iter->ref, iter->msg); + git_vector_foreach(&actual, i, iter) { + if (iter->success) + git_buf_printf(&msg, "%s: success\n", iter->ref); + else + git_buf_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg); + } cl_fail(git_buf_cstr(&msg)); @@ -130,24 +168,11 @@ static void do_verify_push_status(git_push *push, const push_status expected[], */ static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) { - git_vector actual_refs = GIT_VECTOR_INIT; + const git_remote_head **actual_refs; + size_t actual_refs_len; - git_remote_ls(remote, record_ref_cb, &actual_refs); - verify_remote_refs(&actual_refs, expected_refs, expected_refs_len); - - git_vector_free(&actual_refs); -} - -static int tracking_branch_list_cb(const char *branch_name, git_branch_t branch_type, void *payload) -{ - git_vector *tracking = (git_vector *)payload; - - if (branch_type == GIT_BRANCH_REMOTE) - git_vector_insert(tracking, git__strdup(branch_name)); - else - GIT_UNUSED(branch_name); - - return 0; + git_remote_ls(&actual_refs, &actual_refs_len, remote); + verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len); } /** @@ -164,14 +189,24 @@ static void verify_tracking_branches(git_remote *remote, expected_ref expected_r size_t i, j; git_buf msg = GIT_BUF_INIT; git_buf ref_name = GIT_BUF_INIT; - git_buf canonical_ref_name = GIT_BUF_INIT; git_vector actual_refs = GIT_VECTOR_INIT; + git_branch_iterator *iter; char *actual_ref; git_oid oid; - int failed = 0; + int failed = 0, error; + git_branch_t branch_type; + git_reference *ref; /* Get current remote branches */ - cl_git_pass(git_branch_foreach(remote->repo, GIT_BRANCH_REMOTE, tracking_branch_list_cb, &actual_refs)); + cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE)); + + while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) { + cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE); + + cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref)))); + } + + cl_assert_equal_i(error, GIT_ITEROVER); /* Loop through expected refs, make sure they exist */ for (i = 0; i < expected_refs_len; i++) { @@ -187,11 +222,7 @@ static void verify_tracking_branches(git_remote *remote, expected_ref expected_r /* Find matching remote branch */ git_vector_foreach(&actual_refs, j, actual_ref) { - - /* Construct canonical ref name from the actual_ref name */ - git_buf_clear(&canonical_ref_name); - cl_git_pass(git_buf_printf(&canonical_ref_name, "refs/remotes/%s", actual_ref)); - if (!strcmp(git_buf_cstr(&ref_name), git_buf_cstr(&canonical_ref_name))) + if (!strcmp(git_buf_cstr(&ref_name), actual_ref)) break; } @@ -202,7 +233,7 @@ static void verify_tracking_branches(git_remote *remote, expected_ref expected_r } /* Make sure tracking branch is at expected commit ID */ - cl_git_pass(git_reference_name_to_id(&oid, remote->repo, git_buf_cstr(&canonical_ref_name))); + cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref)); if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) { git_buf_puts(&msg, "Tracking branch commit does not match expected ID."); @@ -231,7 +262,6 @@ failed: git_vector_free(&actual_refs); git_buf_free(&msg); - git_buf_free(&canonical_ref_name); git_buf_free(&ref_name); return; } @@ -239,9 +269,9 @@ failed: void test_online_push__initialize(void) { git_vector delete_specs = GIT_VECTOR_INIT; - size_t i; + const git_remote_head **heads; + size_t i, heads_len; char *curr_del_spec; - _cred_acquire_called = false; _repo = cl_git_sandbox_init("push_src"); @@ -272,17 +302,21 @@ void test_online_push__initialize(void) git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e"); git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498"); git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce"); + 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"); + _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); _remote = NULL; if (_remote_url) { cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url)); - git_remote_set_cred_acquire_cb(_remote, cred_acquire_cb, &_cred_acquire_called); record_callbacks_data_clear(&_record_cbs_data); git_remote_set_callbacks(_remote, &_record_cbs); @@ -294,7 +328,8 @@ void test_online_push__initialize(void) * order to delete the remote branch pointed to by HEAD (usually master). * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt */ - cl_git_pass(git_remote_ls(_remote, delete_ref_cb, &delete_specs)); + cl_git_pass(git_remote_ls(&heads, &heads_len, _remote)); + cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len)); if (delete_specs.length) { git_push *push; @@ -314,7 +349,7 @@ void test_online_push__initialize(void) /* Now that we've deleted everything, fetch from the remote */ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(_remote, NULL, NULL)); + cl_git_pass(git_remote_download(_remote)); cl_git_pass(git_remote_update_tips(_remote)); git_remote_disconnect(_remote); } else @@ -336,6 +371,22 @@ void test_online_push__cleanup(void) cl_git_sandbox_cleanup(); } +static int push_pack_progress_cb(int stage, unsigned int current, unsigned int total, void* payload) +{ + int *was_called = (int *) payload; + GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total); + *was_called = 1; + return 0; +} + +static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void* payload) +{ + int *was_called = (int *) payload; + GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes); + *was_called = 1; + return 0; +} + /** * Calls push and relists refs on remote to verify success. * @@ -344,15 +395,17 @@ void test_online_push__cleanup(void) * @param expected_refs expected remote refs after push * @param expected_refs_len length of expected_refs * @param expected_ret expected return value from git_push_finish() + * @param check_progress_cb Check that the push progress callbacks are called */ static void do_push(const char *refspecs[], size_t refspecs_len, push_status expected_statuses[], size_t expected_statuses_len, - expected_ref expected_refs[], size_t expected_refs_len, int expected_ret) + expected_ref expected_refs[], size_t expected_refs_len, int expected_ret, int check_progress_cb) { git_push *push; git_push_options opts = GIT_PUSH_OPTIONS_INIT; size_t i; int ret; + int pack_progress_called = 0, transfer_progress_called = 0; if (_remote) { /* Auto-detect the number of threads to use */ @@ -363,6 +416,9 @@ static void do_push(const char *refspecs[], size_t refspecs_len, cl_git_pass(git_push_new(&push, _remote)); cl_git_pass(git_push_set_options(push, &opts)); + if (check_progress_cb) + cl_git_pass(git_push_set_callbacks(push, push_pack_progress_cb, &pack_progress_called, push_transfer_progress_cb, &transfer_progress_called)); + for (i = 0; i < refspecs_len; i++) cl_git_pass(git_push_add_refspec(push, refspecs[i])); @@ -375,6 +431,11 @@ static void do_push(const char *refspecs[], size_t refspecs_len, cl_assert_equal_i(1, git_push_unpack_ok(push)); } + if (check_progress_cb) { + cl_assert_equal_i(1, pack_progress_called); + cl_assert_equal_i(1, transfer_progress_called); + } + do_verify_push_status(push, expected_statuses, expected_statuses_len); cl_assert_equal_i(expected_ret, ret); @@ -393,57 +454,57 @@ static void do_push(const char *refspecs[], size_t refspecs_len, /* Call push_finish() without ever calling git_push_add_refspec() */ void test_online_push__noop(void) { - do_push(NULL, 0, NULL, 0, NULL, 0, 0); + do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0); } void test_online_push__b1(void) { const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; - push_status exp_stats[] = { { "refs/heads/b1", NULL } }; + push_status exp_stats[] = { { "refs/heads/b1", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__b2(void) { const char *specs[] = { "refs/heads/b2:refs/heads/b2" }; - push_status exp_stats[] = { { "refs/heads/b2", NULL } }; + push_status exp_stats[] = { { "refs/heads/b2", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__b3(void) { const char *specs[] = { "refs/heads/b3:refs/heads/b3" }; - push_status exp_stats[] = { { "refs/heads/b3", NULL } }; + push_status exp_stats[] = { { "refs/heads/b3", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__b4(void) { const char *specs[] = { "refs/heads/b4:refs/heads/b4" }; - push_status exp_stats[] = { { "refs/heads/b4", NULL } }; + push_status exp_stats[] = { { "refs/heads/b4", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__b5(void) { const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; - push_status exp_stats[] = { { "refs/heads/b5", NULL } }; + push_status exp_stats[] = { { "refs/heads/b5", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__multi(void) @@ -456,11 +517,11 @@ void test_online_push__multi(void) "refs/heads/b5:refs/heads/b5" }; push_status exp_stats[] = { - { "refs/heads/b1", NULL }, - { "refs/heads/b2", NULL }, - { "refs/heads/b3", NULL }, - { "refs/heads/b4", NULL }, - { "refs/heads/b5", NULL } + { "refs/heads/b1", 1 }, + { "refs/heads/b2", 1 }, + { "refs/heads/b3", 1 }, + { "refs/heads/b4", 1 }, + { "refs/heads/b5", 1 } }; expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 }, @@ -471,17 +532,17 @@ void test_online_push__multi(void) }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__implicit_tgt(void) { const char *specs1[] = { "refs/heads/b1:" }; - push_status exp_stats1[] = { { "refs/heads/b1", NULL } }; + push_status exp_stats1[] = { { "refs/heads/b1", 1 } }; expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } }; const char *specs2[] = { "refs/heads/b2:" }; - push_status exp_stats2[] = { { "refs/heads/b2", NULL } }; + push_status exp_stats2[] = { { "refs/heads/b2", 1 } }; expected_ref exp_refs2[] = { { "refs/heads/b1", &_oid_b1 }, { "refs/heads/b2", &_oid_b2 } @@ -489,10 +550,10 @@ void test_online_push__implicit_tgt(void) do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1); do_push(specs2, ARRAY_SIZE(specs2), exp_stats2, ARRAY_SIZE(exp_stats2), - exp_refs2, ARRAY_SIZE(exp_refs2), 0); + exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0); } void test_online_push__fast_fwd(void) @@ -500,11 +561,11 @@ void test_online_push__fast_fwd(void) /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */ const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" }; - push_status exp_stats_init[] = { { "refs/heads/b1", NULL } }; + push_status exp_stats_init[] = { { "refs/heads/b1", 1 } }; expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } }; const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" }; - push_status exp_stats_ff[] = { { "refs/heads/b1", NULL } }; + push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } }; expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } }; /* Do a force push to reset b1 in target back to _oid_b1 */ @@ -514,85 +575,95 @@ void test_online_push__fast_fwd(void) do_push(specs_init, ARRAY_SIZE(specs_init), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0); + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1); do_push(specs_ff, ARRAY_SIZE(specs_ff), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0); do_push(specs_reset, ARRAY_SIZE(specs_reset), exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0); + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0); do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0); + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0); } void test_online_push__tag_commit(void) { const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" }; - push_status exp_stats[] = { { "refs/tags/tag-commit", NULL } }; + push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } }; expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__tag_tree(void) { const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" }; - push_status exp_stats[] = { { "refs/tags/tag-tree", NULL } }; + push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } }; expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__tag_blob(void) { const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" }; - push_status exp_stats[] = { { "refs/tags/tag-blob", NULL } }; + push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } }; expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); } void test_online_push__tag_lightweight(void) { const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" }; - push_status exp_stats[] = { { "refs/tags/tag-lightweight", NULL } }; + push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } }; expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } }; do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); +} + +void test_online_push__tag_to_tag(void) +{ + const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" }; + push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 0); } void test_online_push__force(void) { const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; - push_status exp_stats1[] = { { "refs/heads/tgt", NULL } }; + push_status exp_stats1[] = { { "refs/heads/tgt", 1 } }; expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } }; const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"}; const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"}; - push_status exp_stats2_force[] = { { "refs/heads/tgt", NULL } }; + push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } }; expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } }; do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1); do_push(specs2, ARRAY_SIZE(specs2), NULL, 0, - exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD); + exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0); /* Non-fast-forward update with force should pass. */ do_push(specs2_force, ARRAY_SIZE(specs2_force), exp_stats2_force, ARRAY_SIZE(exp_stats2_force), - exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0); + exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1); } void test_online_push__delete(void) @@ -602,8 +673,8 @@ void test_online_push__delete(void) "refs/heads/b1:refs/heads/tgt2" }; push_status exp_stats1[] = { - { "refs/heads/tgt1", NULL }, - { "refs/heads/tgt2", NULL } + { "refs/heads/tgt1", 1 }, + { "refs/heads/tgt2", 1 } }; expected_ref exp_refs1[] = { { "refs/heads/tgt1", &_oid_b1 }, @@ -613,17 +684,17 @@ void test_online_push__delete(void) const char *specs_del_fake[] = { ":refs/heads/fake" }; /* Force has no effect for delete. */ const char *specs_del_fake_force[] = { "+:refs/heads/fake" }; - push_status exp_stats_fake[] = { { "refs/heads/fake", NULL } }; + push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } }; const char *specs_delete[] = { ":refs/heads/tgt1" }; - push_status exp_stats_delete[] = { { "refs/heads/tgt1", NULL } }; + push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } }; expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } }; /* Force has no effect for delete. */ const char *specs_delete_force[] = { "+:refs/heads/tgt1" }; do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1); /* When deleting a non-existent branch, the git client sends zero for both * the old and new commit id. This should succeed on the server with the @@ -633,23 +704,23 @@ void test_online_push__delete(void) */ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0); do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0); /* Delete one of the pushed branches. */ do_push(specs_delete, ARRAY_SIZE(specs_delete), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0); /* Re-push branches and retry delete with force. */ do_push(specs1, ARRAY_SIZE(specs1), exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0); + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0); do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0); + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0); } void test_online_push__bad_refspecs(void) @@ -675,17 +746,19 @@ void test_online_push__expressions(void) /* TODO: Expressions in refspecs doesn't actually work yet */ const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" }; + /* expect not NULL to indicate failure (core git replies "funny refname", + * other servers may be less pithy. */ const char *specs_right_expr[] = { "refs/heads/b2:refs/heads/b2~1" }; - push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", "funny refname" } }; + push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", 0 } }; /* TODO: Find a more precise way of checking errors than a exit code of -1. */ do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), NULL, 0, - NULL, 0, -1); + NULL, 0, -1, 0); do_push(specs_right_expr, ARRAY_SIZE(specs_right_expr), exp_stats_right_expr, ARRAY_SIZE(exp_stats_right_expr), - NULL, 0, 0); + NULL, 0, 0, 1); } void test_online_push__notes(void) @@ -693,7 +766,7 @@ void test_online_push__notes(void) git_oid note_oid, *target_oid, expected_oid; git_signature *signature; const char *specs[] = { "refs/notes/commits:refs/notes/commits" }; - push_status exp_stats[] = { { "refs/notes/commits", NULL } }; + push_status exp_stats[] = { { "refs/notes/commits", 1 } }; expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } }; git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb"); @@ -705,7 +778,7 @@ void test_online_push__notes(void) do_push(specs, ARRAY_SIZE(specs), exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0); + exp_refs, ARRAY_SIZE(exp_refs), 0, 1); git_signature_free(signature); } diff --git a/tests-clar/online/push_util.c b/tests/online/push_util.c similarity index 70% rename from tests-clar/online/push_util.c rename to tests/online/push_util.c index 2e457844d..038c144db 100644 --- a/tests-clar/online/push_util.c +++ b/tests/online/push_util.c @@ -44,20 +44,23 @@ int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid * return 0; } -int delete_ref_cb(git_remote_head *head, void *payload) +int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len) { - git_vector *delete_specs = (git_vector *)payload; git_buf del_spec = GIT_BUF_INIT; + size_t i; - /* Ignore malformed ref names (which also saves us from tag^{} */ - if (!git_reference_is_valid_name(head->name)) - return 0; + for (i = 0; i < heads_len; i++) { + const git_remote_head *head = heads[i]; + /* Ignore malformed ref names (which also saves us from tag^{} */ + if (!git_reference_is_valid_name(head->name)) + return 0; - /* Create a refspec that deletes a branch in the remote */ - if (strcmp(head->name, "refs/heads/master")) { - cl_git_pass(git_buf_putc(&del_spec, ':')); - cl_git_pass(git_buf_puts(&del_spec, head->name)); - cl_git_pass(git_vector_insert(delete_specs, git_buf_detach(&del_spec))); + /* Create a refspec that deletes a branch in the remote */ + if (strcmp(head->name, "refs/heads/master")) { + cl_git_pass(git_buf_putc(&del_spec, ':')); + cl_git_pass(git_buf_puts(&del_spec, head->name)); + cl_git_pass(git_vector_insert(out, git_buf_detach(&del_spec))); + } } return 0; @@ -69,26 +72,28 @@ int record_ref_cb(git_remote_head *head, void *payload) return git_vector_insert(refs, head); } -void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len) +void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len) { size_t i, j = 0; git_buf msg = GIT_BUF_INIT; - git_remote_head *actual; + const git_remote_head *actual; char *oid_str; bool master_present = false; /* We don't care whether "master" is present on the other end or not */ - git_vector_foreach(actual_refs, i, actual) { + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; if (!strcmp(actual->name, "refs/heads/master")) { master_present = true; break; } } - if (expected_refs_len + (master_present ? 1 : 0) != actual_refs->length) + if (expected_refs_len + (master_present ? 1 : 0) != actual_refs_len) goto failed; - git_vector_foreach(actual_refs, i, actual) { + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; if (master_present && !strcmp(actual->name, "refs/heads/master")) continue; @@ -111,7 +116,8 @@ failed: } git_buf_puts(&msg, "\nACTUAL:\n"); - git_vector_foreach(actual_refs, i, actual) { + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; if (master_present && !strcmp(actual->name, "refs/heads/master")) continue; diff --git a/tests-clar/online/push_util.h b/tests/online/push_util.h similarity index 69% rename from tests-clar/online/push_util.h rename to tests/online/push_util.h index 759122aa6..a7207c49e 100644 --- a/tests-clar/online/push_util.h +++ b/tests/online/push_util.h @@ -12,7 +12,7 @@ extern const git_oid OID_ZERO; * @param data pointer to a record_callbacks_data instance */ #define RECORD_CALLBACKS_INIT(data) \ - { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, record_update_tips_cb, data } + { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, record_update_tips_cb, data } typedef struct { char *name; @@ -41,12 +41,13 @@ void record_callbacks_data_clear(record_callbacks_data *data); int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data); /** - * Callback for git_remote_list that adds refspecs to delete each ref + * Create a set of refspecs that deletes each of the inputs * - * @param head a ref on the remote - * @param payload a git_push instance + * @param out the vector in which to store the refspecs + * @param heads the remote heads + * @param heads_len the size of the array */ -int delete_ref_cb(git_remote_head *head, void *payload); +int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len); /** * Callback for git_remote_list that adds refspecs to vector @@ -60,10 +61,11 @@ int record_ref_cb(git_remote_head *head, void *payload); * Verifies that refs on remote stored by record_ref_cb match the expected * names, oids, and order. * - * @param actual_refs actual refs stored by record_ref_cb() + * @param actual_refs actual refs in the remote + * @param actual_refs_len length of actual_refs * @param expected_refs expected remote refs * @param expected_refs_len length of expected_refs */ -void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len); +void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len); #endif /* INCLUDE_cl_push_util_h__ */ diff --git a/tests/pack/indexer.c b/tests/pack/indexer.c new file mode 100644 index 000000000..07963a9e7 --- /dev/null +++ b/tests/pack/indexer.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include +#include "fileops.h" +#include "hash.h" +#include "iterator.h" +#include "vector.h" +#include "posix.h" + + +/* + * This is a packfile with three objects. The second is a delta which + * depends on the third, which is also a delta. + */ +unsigned char out_of_order_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, + 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, + 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, + 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, + 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x75, 0x01, + 0xd7, 0x71, 0x36, 0x66, 0xf4, 0xde, 0x82, 0x27, 0x76, 0xc7, 0x62, 0x2c, + 0x10, 0xf1, 0xb0, 0x7d, 0xe2, 0x80, 0xdc, 0x78, 0x9c, 0x63, 0x62, 0x62, + 0x62, 0xb7, 0x03, 0x00, 0x00, 0x69, 0x00, 0x4c, 0xde, 0x7d, 0xaa, 0xe4, + 0x19, 0x87, 0x58, 0x80, 0x61, 0x09, 0x9a, 0x33, 0xca, 0x7a, 0x31, 0x92, + 0x6f, 0xae, 0x66, 0x75 +}; +unsigned int out_of_order_pack_len = 112; + +/* + * Packfile with two objects. The second is a delta against an object + * which is not in the packfile + */ +unsigned char thin_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, + 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, + 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, + 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x42, 0x52, + 0x3a, 0x6f, 0x39, 0xd1, 0xfe, 0x66, 0x68, 0x6b, 0xa5, 0xe5, 0xe2, 0x97, + 0xac, 0x94, 0x6c, 0x76, 0x0b, 0x04 +}; +unsigned int thin_pack_len = 78; + +unsigned char base_obj[] = { 07, 076 }; +unsigned int base_obj_len = 2; + +void test_pack_indexer__out_of_order(void) +{ + git_indexer *idx; + git_transfer_progress stats; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); + cl_git_pass(git_indexer_append(idx, out_of_order_pack, out_of_order_pack_len, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 3); + cl_assert_equal_i(stats.received_objects, 3); + cl_assert_equal_i(stats.indexed_objects, 3); + + git_indexer_free(idx); +} + +void test_pack_indexer__fix_thin(void) +{ + git_indexer *idx; + git_transfer_progress stats; + git_repository *repo; + git_odb *odb; + git_oid id, should_id; + + cl_git_pass(git_repository_init(&repo, "thin.git", true)); + cl_git_pass(git_repository_odb(&odb, repo)); + + /* Store the missing base into your ODB so the indexer can fix the pack */ + cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB)); + git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); + cl_assert(!git_oid_cmp(&id, &should_id)); + + cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL)); + cl_git_pass(git_indexer_append(idx, thin_pack, thin_pack_len, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 2); + cl_assert_equal_i(stats.received_objects, 2); + cl_assert_equal_i(stats.indexed_objects, 2); + cl_assert_equal_i(stats.local_objects, 1); + + git_oid_fromstr(&should_id, "11f0f69b334728fdd8bc86b80499f22f29d85b15"); + cl_assert(!git_oid_cmp(git_indexer_hash(idx), &should_id)); + + git_indexer_free(idx); + git_odb_free(odb); + git_repository_free(repo); + + /* + * The pack's name/hash only tells us what objects there are, + * so we need to go through the packfile again in order to + * figure out whether we calculated the trailer correctly. + */ + { + unsigned char buffer[128]; + int fd; + ssize_t read; + struct stat st; + const char *name = "pack-11f0f69b334728fdd8bc86b80499f22f29d85b15.pack"; + + fd = p_open(name, O_RDONLY); + cl_assert(fd != -1); + + cl_git_pass(p_stat(name, &st)); + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); + read = p_read(fd, buffer, sizeof(buffer)); + cl_assert(read != -1); + p_close(fd); + + cl_git_pass(git_indexer_append(idx, buffer, read, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 3); + cl_assert_equal_i(stats.received_objects, 3); + cl_assert_equal_i(stats.indexed_objects, 3); + cl_assert_equal_i(stats.local_objects, 0); + + git_indexer_free(idx); + } +} diff --git a/tests-clar/pack/packbuilder.c b/tests/pack/packbuilder.c similarity index 62% rename from tests-clar/pack/packbuilder.c rename to tests/pack/packbuilder.c index 764fba213..1ae2322a5 100644 --- a/tests-clar/pack/packbuilder.c +++ b/tests/pack/packbuilder.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "fileops.h" +#include "pack.h" #include "hash.h" #include "iterator.h" #include "vector.h" @@ -8,7 +9,7 @@ static git_repository *_repo; static git_revwalk *_revwalker; static git_packbuilder *_packbuilder; -static git_indexer_stream *_indexer; +static git_indexer *_indexer; static git_vector _commits; static int _commits_is_initialized; @@ -40,7 +41,7 @@ void test_pack_packbuilder__cleanup(void) git_revwalk_free(_revwalker); _revwalker = NULL; - git_indexer_stream_free(_indexer); + git_indexer_free(_indexer); _indexer = NULL; cl_git_sandbox_cleanup(); @@ -79,7 +80,7 @@ static int feed_indexer(void *ptr, size_t len, void *payload) { git_transfer_progress *stats = (git_transfer_progress *)payload; - return git_indexer_stream_add(_indexer, ptr, len, stats); + return git_indexer_append(_indexer, ptr, len, stats); } void test_pack_packbuilder__create_pack(void) @@ -92,11 +93,11 @@ void test_pack_packbuilder__create_pack(void) seed_packbuilder(); - cl_git_pass(git_indexer_stream_new(&_indexer, ".", NULL, NULL)); + cl_git_pass(git_indexer_new(&_indexer, ".", 0, NULL, NULL, NULL)); cl_git_pass(git_packbuilder_foreach(_packbuilder, feed_indexer, &stats)); - cl_git_pass(git_indexer_stream_finalize(_indexer, &stats)); + cl_git_pass(git_indexer_commit(_indexer, &stats)); - git_oid_fmt(hex, git_indexer_stream_hash(_indexer)); + git_oid_fmt(hex, git_indexer_hash(_indexer)); git_buf_printf(&path, "pack-%s.pack", hex); /* @@ -105,7 +106,7 @@ void test_pack_packbuilder__create_pack(void) * we create exactly the same pack as git.git does when *not* * reusing existing deltas (as libgit2). * - * $ cd tests-clar/resources/testrepo.git + * $ cd tests/resources/testrepo.git * $ git rev-list --objects HEAD | \ * git pack-objects -q --no-reuse-delta --threads=1 pack * $ sha1sum git-80e61eb315239ef3c53033e37fee43b744d57122.pack @@ -128,21 +129,76 @@ void test_pack_packbuilder__create_pack(void) cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b"); } +void test_pack_packbuilder__get_hash(void) +{ + char hex[41]; hex[40] = '\0'; + + seed_packbuilder(); + + git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL); + git_oid_fmt(hex, git_packbuilder_hash(_packbuilder)); + + cl_assert_equal_s(hex, "80e61eb315239ef3c53033e37fee43b744d57122"); +} + +static void test_write_pack_permission(mode_t given, mode_t expected) +{ + struct stat statbuf; + mode_t mask, os_mask; + + seed_packbuilder(); + + git_packbuilder_write(_packbuilder, ".", given, NULL, NULL); + + /* Windows does not return group/user bits from stat, + * files are never executable. + */ +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + mask = p_umask(0); + p_umask(mask); + + cl_git_pass(p_stat("pack-80e61eb315239ef3c53033e37fee43b744d57122.idx", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); + + cl_git_pass(p_stat("pack-80e61eb315239ef3c53033e37fee43b744d57122.pack", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); +} + +void test_pack_packbuilder__permissions_standard(void) +{ + test_write_pack_permission(0, GIT_PACK_FILE_MODE); +} + +void test_pack_packbuilder__permissions_readonly(void) +{ + test_write_pack_permission(0444, 0444); +} + +void test_pack_packbuilder__permissions_readwrite(void) +{ + test_write_pack_permission(0666, 0666); +} + static git_transfer_progress stats; static int foreach_cb(void *buf, size_t len, void *payload) { - git_indexer_stream *idx = (git_indexer_stream *) payload; - cl_git_pass(git_indexer_stream_add(idx, buf, len, &stats)); + git_indexer *idx = (git_indexer *) payload; + cl_git_pass(git_indexer_append(idx, buf, len, &stats)); return 0; } void test_pack_packbuilder__foreach(void) { - git_indexer_stream *idx; + git_indexer *idx; seed_packbuilder(); - cl_git_pass(git_indexer_stream_new(&idx, ".", NULL, NULL)); + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL)); cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx)); - cl_git_pass(git_indexer_stream_finalize(idx, &stats)); - git_indexer_stream_free(idx); + cl_git_pass(git_indexer_commit(idx, &stats)); + git_indexer_free(idx); } diff --git a/tests-clar/refs/branches/create.c b/tests/refs/branches/create.c similarity index 100% rename from tests-clar/refs/branches/create.c rename to tests/refs/branches/create.c diff --git a/tests-clar/refs/branches/delete.c b/tests/refs/branches/delete.c similarity index 98% rename from tests-clar/refs/branches/delete.c rename to tests/refs/branches/delete.c index 7af5a3e86..de90cb734 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests/refs/branches/delete.c @@ -57,11 +57,11 @@ void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void git_reference_free(branch); } -void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_orphaned(void) +void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_unborn(void) { git_reference *branch; - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); diff --git a/tests-clar/refs/branches/ishead.c b/tests/refs/branches/ishead.c similarity index 96% rename from tests-clar/refs/branches/ishead.c rename to tests/refs/branches/ishead.c index dfcf1b5f1..b1ad09c3e 100644 --- a/tests-clar/refs/branches/ishead.c +++ b/tests/refs/branches/ishead.c @@ -26,13 +26,13 @@ void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void) cl_assert_equal_i(true, git_branch_is_head(branch)); } -void test_refs_branches_ishead__can_properly_handle_orphaned_HEAD(void) +void test_refs_branches_ishead__can_properly_handle_unborn_HEAD(void) { git_repository_free(repo); repo = cl_git_sandbox_init("testrepo.git"); - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); diff --git a/tests-clar/refs/branches/foreach.c b/tests/refs/branches/iterator.c similarity index 55% rename from tests-clar/refs/branches/foreach.c rename to tests/refs/branches/iterator.c index 433812cb4..904c6a146 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests/refs/branches/iterator.c @@ -4,7 +4,7 @@ static git_repository *repo; static git_reference *fake_remote; -void test_refs_branches_foreach__initialize(void) +void test_refs_branches_iterator__initialize(void) { git_oid id; @@ -15,7 +15,7 @@ void test_refs_branches_foreach__initialize(void) cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0)); } -void test_refs_branches_foreach__cleanup(void) +void test_refs_branches_iterator__cleanup(void) { git_reference_free(fake_remote); fake_remote = NULL; @@ -28,39 +28,35 @@ void test_refs_branches_foreach__cleanup(void) cl_git_sandbox_cleanup(); } -static int count_branch_list_cb(const char *branch_name, git_branch_t branch_type, void *payload) -{ - int *count; - - GIT_UNUSED(branch_type); - GIT_UNUSED(branch_name); - - count = (int *)payload; - (*count)++; - - return 0; -} - static void assert_retrieval(unsigned int flags, unsigned int expected_count) { - int count = 0; + git_branch_iterator *iter; + git_reference *ref; + int count = 0, error; + git_branch_t type; - cl_git_pass(git_branch_foreach(repo, flags, count_branch_list_cb, &count)); + cl_git_pass(git_branch_iterator_new(&iter, repo, flags)); + while ((error = git_branch_next(&ref, &type, iter)) == 0) { + count++; + git_reference_free(ref); + } + git_branch_iterator_free(iter); + cl_assert_equal_i(error, GIT_ITEROVER); cl_assert_equal_i(expected_count, count); } -void test_refs_branches_foreach__retrieve_all_branches(void) +void test_refs_branches_iterator__retrieve_all_branches(void) { assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 14); } -void test_refs_branches_foreach__retrieve_remote_branches(void) +void test_refs_branches_iterator__retrieve_remote_branches(void) { assert_retrieval(GIT_BRANCH_REMOTE, 2); } -void test_refs_branches_foreach__retrieve_local_branches(void) +void test_refs_branches_iterator__retrieve_local_branches(void) { assert_retrieval(GIT_BRANCH_LOCAL, 12); } @@ -84,21 +80,22 @@ static void assert_branch_has_been_found(struct expectations *findings, const ch cl_fail("expected branch not found in list."); } -static int contains_branch_list_cb(const char *branch_name, git_branch_t branch_type, void *payload) +static void contains_branches(struct expectations exp[], git_branch_iterator *iter) { - int pos = 0; - struct expectations *exp; + git_reference *ref; + git_branch_t type; + int error, pos = 0; - GIT_UNUSED(branch_type); + while ((error = git_branch_next(&ref, &type, iter)) == 0) { + for (pos = 0; exp[pos].branch_name; ++pos) { + if (strcmp(git_reference_shorthand(ref), exp[pos].branch_name) == 0) + exp[pos].encounters++; + } - exp = (struct expectations *)payload; - - for (pos = 0; exp[pos].branch_name; ++pos) { - if (strcmp(branch_name, exp[pos].branch_name) == 0) - exp[pos].encounters++; + git_reference_free(ref); } - return 0; + cl_assert_equal_i(error, GIT_ITEROVER); } /* @@ -106,8 +103,9 @@ static int contains_branch_list_cb(const char *branch_name, git_branch_t branch_ * nulltoken/HEAD -> nulltoken/master * nulltoken/master */ -void test_refs_branches_foreach__retrieve_remote_symbolic_HEAD_when_present(void) +void test_refs_branches_iterator__retrieve_remote_symbolic_HEAD_when_present(void) { + git_branch_iterator *iter; struct expectations exp[] = { { "nulltoken/HEAD", 0 }, { "nulltoken/master", 0 }, @@ -119,39 +117,17 @@ void test_refs_branches_foreach__retrieve_remote_symbolic_HEAD_when_present(void assert_retrieval(GIT_BRANCH_REMOTE, 3); - cl_git_pass(git_branch_foreach(repo, GIT_BRANCH_REMOTE, contains_branch_list_cb, &exp)); + cl_git_pass(git_branch_iterator_new(&iter, repo, GIT_BRANCH_REMOTE)); + contains_branches(exp, iter); + git_branch_iterator_free(iter); assert_branch_has_been_found(exp, "nulltoken/HEAD"); assert_branch_has_been_found(exp, "nulltoken/master"); } -static int branch_list_interrupt_cb( - const char *branch_name, git_branch_t branch_type, void *payload) -{ - int *count; - - GIT_UNUSED(branch_type); - GIT_UNUSED(branch_name); - - count = (int *)payload; - (*count)++; - - return (*count == 5); -} - -void test_refs_branches_foreach__can_cancel(void) -{ - int count = 0; - - cl_assert_equal_i(GIT_EUSER, - git_branch_foreach(repo, GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, - branch_list_interrupt_cb, &count)); - - cl_assert_equal_i(5, count); -} - -void test_refs_branches_foreach__mix_of_packed_and_loose(void) +void test_refs_branches_iterator__mix_of_packed_and_loose(void) { + git_branch_iterator *iter; struct expectations exp[] = { { "master", 0 }, { "origin/HEAD", 0 }, @@ -163,8 +139,10 @@ void test_refs_branches_foreach__mix_of_packed_and_loose(void) r2 = cl_git_sandbox_init("testrepo2"); - cl_git_pass(git_branch_foreach(r2, GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, - contains_branch_list_cb, &exp)); + cl_git_pass(git_branch_iterator_new(&iter, r2, GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE)); + contains_branches(exp, iter); + + git_branch_iterator_free(iter); assert_branch_has_been_found(exp, "master"); assert_branch_has_been_found(exp, "origin/HEAD"); diff --git a/tests-clar/refs/branches/lookup.c b/tests/refs/branches/lookup.c similarity index 100% rename from tests-clar/refs/branches/lookup.c rename to tests/refs/branches/lookup.c diff --git a/tests-clar/refs/branches/move.c b/tests/refs/branches/move.c similarity index 100% rename from tests-clar/refs/branches/move.c rename to tests/refs/branches/move.c diff --git a/tests-clar/refs/branches/name.c b/tests/refs/branches/name.c similarity index 100% rename from tests-clar/refs/branches/name.c rename to tests/refs/branches/name.c diff --git a/tests-clar/refs/branches/remote.c b/tests/refs/branches/remote.c similarity index 100% rename from tests-clar/refs/branches/remote.c rename to tests/refs/branches/remote.c diff --git a/tests-clar/refs/branches/upstream.c b/tests/refs/branches/upstream.c similarity index 100% rename from tests-clar/refs/branches/upstream.c rename to tests/refs/branches/upstream.c diff --git a/tests-clar/refs/branches/upstreamname.c b/tests/refs/branches/upstreamname.c similarity index 100% rename from tests-clar/refs/branches/upstreamname.c rename to tests/refs/branches/upstreamname.c diff --git a/tests-clar/refs/crashes.c b/tests/refs/crashes.c similarity index 100% rename from tests-clar/refs/crashes.c rename to tests/refs/crashes.c diff --git a/tests-clar/refs/create.c b/tests/refs/create.c similarity index 100% rename from tests-clar/refs/create.c rename to tests/refs/create.c diff --git a/tests-clar/refs/delete.c b/tests/refs/delete.c similarity index 100% rename from tests-clar/refs/delete.c rename to tests/refs/delete.c diff --git a/tests-clar/refs/foreachglob.c b/tests/refs/foreachglob.c similarity index 100% rename from tests-clar/refs/foreachglob.c rename to tests/refs/foreachglob.c diff --git a/tests-clar/refs/isvalidname.c b/tests/refs/isvalidname.c similarity index 100% rename from tests-clar/refs/isvalidname.c rename to tests/refs/isvalidname.c diff --git a/tests-clar/refs/iterator.c b/tests/refs/iterator.c similarity index 100% rename from tests-clar/refs/iterator.c rename to tests/refs/iterator.c diff --git a/tests-clar/refs/list.c b/tests/refs/list.c similarity index 92% rename from tests-clar/refs/list.c rename to tests/refs/list.c index c9c2af4a7..de5c0fd3d 100644 --- a/tests-clar/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 13); + cl_assert_equal_i((int)ref_list.count, 14); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 13); + cl_assert_equal_i((int)ref_list.count, 14); git_strarray_free(&ref_list); } diff --git a/tests-clar/refs/listall.c b/tests/refs/listall.c similarity index 100% rename from tests-clar/refs/listall.c rename to tests/refs/listall.c diff --git a/tests-clar/refs/lookup.c b/tests/refs/lookup.c similarity index 80% rename from tests-clar/refs/lookup.c rename to tests/refs/lookup.c index 0dbebc5c2..2e31cf0f6 100644 --- a/tests-clar/refs/lookup.c +++ b/tests/refs/lookup.c @@ -46,3 +46,15 @@ void test_refs_lookup__oid(void) cl_git_pass(git_oid_fromstr(&expected, "1385f264afb75a56a5bec74243be9b367ba4ca08")); cl_assert(git_oid_cmp(&tag, &expected) == 0); } + +void test_refs_lookup__namespace(void) +{ + int error; + git_reference *ref; + + error = git_reference_lookup(&ref, g_repo, "refs/heads"); + cl_assert_equal_i(error, GIT_ENOTFOUND); + + error = git_reference_lookup(&ref, g_repo, "refs/heads/"); + cl_assert_equal_i(error, GIT_EINVALIDSPEC); +} diff --git a/tests-clar/refs/normalize.c b/tests/refs/normalize.c similarity index 100% rename from tests-clar/refs/normalize.c rename to tests/refs/normalize.c diff --git a/tests-clar/refs/overwrite.c b/tests/refs/overwrite.c similarity index 100% rename from tests-clar/refs/overwrite.c rename to tests/refs/overwrite.c diff --git a/tests-clar/refs/pack.c b/tests/refs/pack.c similarity index 73% rename from tests-clar/refs/pack.c rename to tests/refs/pack.c index d8d5cc6d0..849a052aa 100644 --- a/tests-clar/refs/pack.c +++ b/tests/refs/pack.c @@ -32,7 +32,7 @@ static void packall(void) void test_refs_pack__empty(void) { - // create a packfile for an empty folder + /* create a packfile for an empty folder */ 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")); @@ -44,7 +44,7 @@ void test_refs_pack__empty(void) void test_refs_pack__loose(void) { - // create a packfile from all the loose rn a repo + /* create a packfile from all the loose refs in a repo */ git_reference *reference; git_buf temp_path = GIT_BUF_INIT; @@ -77,3 +77,29 @@ void test_refs_pack__loose(void) git_reference_free(reference); git_buf_free(&temp_path); } + +void test_refs_pack__symbolic(void) +{ + /* create a packfile from loose refs skipping symbolic refs */ + int i; + git_oid head; + git_reference *ref; + char name[128]; + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (i = 0; i < 100; ++i) { + snprintf(name, sizeof(name), "refs/heads/symbolic-%03d", i); + cl_git_pass(git_reference_symbolic_create( + &ref, g_repo, name, "refs/heads/master", 0)); + git_reference_free(ref); + + snprintf(name, sizeof(name), "refs/heads/direct-%03d", i); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + packall(); +} diff --git a/tests-clar/refs/peel.c b/tests/refs/peel.c similarity index 100% rename from tests-clar/refs/peel.c rename to tests/refs/peel.c diff --git a/tests-clar/refs/read.c b/tests/refs/read.c similarity index 94% rename from tests-clar/refs/read.c rename to tests/refs/read.c index afb6be008..35cf17e9e 100644 --- a/tests-clar/refs/read.c +++ b/tests/refs/read.c @@ -255,6 +255,22 @@ void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) assert_is_branch("refs/tags/e90810b", false); } +static void assert_is_tag(const char *name, bool expected_tagness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_tagness, git_reference_is_tag(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_tag(void) +{ + assert_is_tag("refs/tags/e90810b", true); + assert_is_tag("refs/tags/test", true); + assert_is_tag("refs/heads/packed", false); + assert_is_tag("refs/remotes/test/master", false); +} + void test_refs_read__invalid_name_returns_EINVALIDSPEC(void) { git_reference *reference; diff --git a/tests-clar/refs/ref_helpers.c b/tests/refs/ref_helpers.c similarity index 100% rename from tests-clar/refs/ref_helpers.c rename to tests/refs/ref_helpers.c diff --git a/tests-clar/refs/ref_helpers.h b/tests/refs/ref_helpers.h similarity index 100% rename from tests-clar/refs/ref_helpers.h rename to tests/refs/ref_helpers.h diff --git a/tests-clar/refs/reflog/drop.c b/tests/refs/reflog/drop.c similarity index 91% rename from tests-clar/refs/reflog/drop.c rename to tests/refs/reflog/drop.c index 21cc847bf..916bd9933 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests/refs/reflog/drop.c @@ -8,15 +8,10 @@ static size_t entrycount; void test_refs_reflog_drop__initialize(void) { - git_reference *ref; - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - git_reflog_read(&g_reflog, ref); + git_reflog_read(&g_reflog, g_repo, "HEAD"); entrycount = git_reflog_entrycount(g_reflog); - - git_reference_free(ref); } void test_refs_reflog_drop__cleanup(void) @@ -106,19 +101,15 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void) void test_refs_reflog_drop__can_persist_deletion_on_disk(void) { - git_reference *ref; - cl_assert(entrycount > 2); - cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); cl_git_pass(git_reflog_write(g_reflog)); git_reflog_free(g_reflog); - git_reflog_read(&g_reflog, ref); - git_reference_free(ref); + git_reflog_read(&g_reflog, g_repo, "HEAD"); cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); } diff --git a/tests-clar/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c similarity index 74% rename from tests-clar/refs/reflog/reflog.c rename to tests/refs/reflog/reflog.c index 095cabf04..bcd224270 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests/refs/reflog/reflog.c @@ -13,7 +13,7 @@ static git_repository *g_repo; // helpers -static void assert_signature(git_signature *expected, git_signature *actual) +static void assert_signature(const git_signature *expected, const git_signature *actual) { cl_assert(actual); cl_assert_equal_s(expected->name, actual->name); @@ -34,30 +34,13 @@ void test_refs_reflog_reflog__cleanup(void) cl_git_sandbox_cleanup(); } -void test_refs_reflog_reflog__append_then_read(void) +static void assert_appends(const git_signature *committer, const git_oid *oid) { - // write a reflog for a given reference and ensure it can be read back git_repository *repo2; - git_reference *ref, *lookedup_ref; - git_oid oid; - git_signature *committer; + git_reference *lookedup_ref; git_reflog *reflog; const git_reflog_entry *entry; - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&oid, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0)); - - cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - - cl_git_pass(git_reflog_read(&reflog, ref)); - - cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); - cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); - cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); - cl_git_pass(git_reflog_write(reflog)); - git_reflog_free(reflog); - /* Reopen a new instance of the repository */ cl_git_pass(git_repository_open(&repo2, "testrepo.git")); @@ -65,29 +48,78 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); /* Read and parse the reflog for this branch */ - cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); + cl_git_pass(git_reflog_read(&reflog, repo2, new_ref)); cl_assert_equal_i(2, (int)git_reflog_entrycount(reflog)); entry = git_reflog_entry_byindex(reflog, 1); assert_signature(committer, entry->committer); cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); - cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); cl_assert(entry->msg == NULL); entry = git_reflog_entry_byindex(reflog, 0); assert_signature(committer, entry->committer); - cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0); - cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); cl_assert_equal_s(commit_msg, entry->msg); - git_signature_free(committer); git_reflog_free(reflog); git_repository_free(repo2); - git_reference_free(ref); git_reference_free(lookedup_ref); } +void test_refs_reflog_reflog__append_then_read(void) +{ + /* write a reflog for a given reference and ensure it can be read back */ + git_reference *ref; + git_oid oid; + git_signature *committer; + git_reflog *reflog; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0)); + git_reference_free(ref); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); + + cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); + cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_write(reflog)); + git_reflog_free(reflog); + + assert_appends(committer, &oid); + + git_signature_free(committer); +} + +void test_refs_reflog_reflog__append_to_then_read(void) +{ + /* write a reflog for a given reference and ensure it can be read back */ + git_reference *ref; + git_oid oid; + git_signature *committer; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0)); + git_reference_free(ref); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_fail(git_reflog_append_to(g_repo, new_ref, &oid, committer, "no inner\nnewline")); + cl_git_pass(git_reflog_append_to(g_repo, new_ref, &oid, committer, NULL)); + cl_git_pass(git_reflog_append_to(g_repo, new_ref, &oid, committer, commit_msg "\n")); + + assert_appends(committer, &oid); + + git_signature_free(committer); +} + void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) { git_reference *master, *new_master; @@ -133,21 +165,18 @@ void test_refs_reflog_reflog__reference_has_reflog(void) void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) { - git_reference *subtrees; git_reflog *reflog; + const char *refname = "refs/heads/subtrees"; git_buf subtrees_log_path = GIT_BUF_INIT; - cl_git_pass(git_reference_lookup(&subtrees, g_repo, "refs/heads/subtrees")); - - git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, git_reference_name(subtrees)); + git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path))); - cl_git_pass(git_reflog_read(&reflog, subtrees)); + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog)); git_reflog_free(reflog); - git_reference_free(subtrees); git_buf_free(&subtrees_log_path); } @@ -158,7 +187,7 @@ void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) git_reflog *reflog; cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reflog_read(&reflog, master)); + cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master")); cl_git_pass(git_reflog_write(reflog)); @@ -175,12 +204,6 @@ void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void) { - git_reference *master; - - cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_reflog_rename(master, "refs/heads/Inv@{id")); - - git_reference_free(master); + git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id")); } diff --git a/tests-clar/refs/rename.c b/tests/refs/rename.c similarity index 100% rename from tests-clar/refs/rename.c rename to tests/refs/rename.c diff --git a/tests-clar/refs/revparse.c b/tests/refs/revparse.c similarity index 93% rename from tests-clar/refs/revparse.c rename to tests/refs/revparse.c index 69d92745c..37d3981bb 100644 --- a/tests-clar/refs/revparse.c +++ b/tests/refs/revparse.c @@ -544,6 +544,37 @@ void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void) GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "e90")); } +/* + * $ echo "aabqhq" | git hash-object -t blob --stdin + * dea509d0b3cb8ee0650f6ca210bc83f4678851ba + * + * $ echo "aaazvc" | git hash-object -t blob --stdin + * dea509d097ce692e167dfc6a48a7a280cc5e877e + */ +void test_refs_revparse__a_not_precise_enough_objectid_returns_EAMBIGUOUS(void) +{ + git_repository *repo; + git_index *index; + git_object *obj; + + repo = cl_git_sandbox_init("testrepo"); + + cl_git_mkfile("testrepo/one.txt", "aabqhq\n"); + cl_git_mkfile("testrepo/two.txt", "aaazvc\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "one.txt")); + cl_git_pass(git_index_add_bypath(index, "two.txt")); + + cl_git_fail_with(git_revparse_single(&obj, repo, "dea509d0"), GIT_EAMBIGUOUS); + + cl_git_pass(git_revparse_single(&obj, repo, "dea509d09")); + + git_object_free(obj); + git_index_free(index); + cl_git_sandbox_cleanup(); +} + void test_refs_revparse__issue_994(void) { git_repository *repo; @@ -738,4 +769,45 @@ void test_refs_revparse__ext_can_expand_short_reference_names(void) "master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "refs/heads/master"); + + test_object_and_ref( + "HEAD", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "refs/heads/master"); + + test_object_and_ref( + "tags/test", + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", + "refs/tags/test"); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_revision(void) +{ + test_object_and_ref( + "HEAD~3", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + NULL); + + test_object_and_ref( + "HEAD~0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "HEAD^0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "@{-1}@{0}", + "a4a7dce85cf63874e984719f4fdd239f5145052f", + NULL); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_tree_content(void) +{ + test_object_and_ref( + "tags/test:readme.txt", + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", + NULL); } diff --git a/tests-clar/refs/setter.c b/tests/refs/setter.c similarity index 100% rename from tests-clar/refs/setter.c rename to tests/refs/setter.c diff --git a/tests-clar/refs/shorthand.c b/tests/refs/shorthand.c similarity index 100% rename from tests-clar/refs/shorthand.c rename to tests/refs/shorthand.c diff --git a/tests-clar/refs/unicode.c b/tests/refs/unicode.c similarity index 52% rename from tests-clar/refs/unicode.c rename to tests/refs/unicode.c index 2ec103275..b56012869 100644 --- a/tests-clar/refs/unicode.c +++ b/tests/refs/unicode.c @@ -4,17 +4,13 @@ static git_repository *repo; void test_refs_unicode__initialize(void) { - cl_fixture_sandbox("testrepo.git"); - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + repo = cl_git_sandbox_init("testrepo.git"); } void test_refs_unicode__cleanup(void) { - git_repository_free(repo); + cl_git_sandbox_cleanup(); repo = NULL; - - cl_fixture_cleanup("testrepo.git"); } void test_refs_unicode__create_and_lookup(void) @@ -22,23 +18,39 @@ void test_refs_unicode__create_and_lookup(void) git_reference *ref0, *ref1, *ref2; git_repository *repo2; - const char *REFNAME = "refs/heads/" "\305" "ngstr" "\366" "m"; + const char *REFNAME = "refs/heads/" "\303\205" "ngstr" "\303\266" "m"; const char *master = "refs/heads/master"; /* Create the reference */ cl_git_pass(git_reference_lookup(&ref0, repo, master)); - cl_git_pass(git_reference_create(&ref1, repo, REFNAME, git_reference_target(ref0), 0)); + cl_git_pass(git_reference_create( + &ref1, repo, REFNAME, git_reference_target(ref0), 0)); cl_assert_equal_s(REFNAME, git_reference_name(ref1)); + git_reference_free(ref0); /* Lookup the reference in a different instance of the repository */ cl_git_pass(git_repository_open(&repo2, "testrepo.git")); + cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME)); - - cl_assert(git_oid_cmp(git_reference_target(ref1), git_reference_target(ref2)) == 0); + cl_assert_equal_i( + 0, git_oid_cmp(git_reference_target(ref1), git_reference_target(ref2))); cl_assert_equal_s(REFNAME, git_reference_name(ref2)); - - git_reference_free(ref0); - git_reference_free(ref1); git_reference_free(ref2); + +#if GIT_USE_ICONV + /* Lookup reference by decomposed unicode name */ + +#define REFNAME_DECOMPOSED "refs/heads/" "A" "\314\212" "ngstro" "\314\210" "m" + + cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME_DECOMPOSED)); + cl_assert_equal_i( + 0, git_oid_cmp(git_reference_target(ref1), git_reference_target(ref2))); + cl_assert_equal_s(REFNAME, git_reference_name(ref2)); + git_reference_free(ref2); +#endif + + /* Cleanup */ + + git_reference_free(ref1); git_repository_free(repo2); } diff --git a/tests-clar/refs/update.c b/tests/refs/update.c similarity index 100% rename from tests-clar/refs/update.c rename to tests/refs/update.c diff --git a/tests-clar/repo/config.c b/tests/repo/config.c similarity index 97% rename from tests-clar/repo/config.c rename to tests/repo/config.c index b8971bb6b..e77acc8c5 100644 --- a/tests-clar/repo/config.c +++ b/tests/repo/config.c @@ -2,7 +2,7 @@ #include "fileops.h" #include -git_buf path = GIT_BUF_INIT; +static git_buf path = GIT_BUF_INIT; void test_repo_config__initialize(void) { @@ -46,6 +46,8 @@ void test_repo_config__open_missing_global(void) git_config_free(global); git_config_free(config); git_repository_free(repo); + + git_futils_dirs_global_shutdown(); } void test_repo_config__open_missing_global_with_separators(void) @@ -73,6 +75,8 @@ void test_repo_config__open_missing_global_with_separators(void) git_config_free(global); git_config_free(config); git_repository_free(repo); + + git_futils_dirs_global_shutdown(); } #include "repository.h" @@ -101,6 +105,8 @@ void test_repo_config__read_no_configs(void) cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); git_repository_free(repo); + git_futils_dirs_global_shutdown(); + /* with just system */ cl_must_pass(p_mkdir("alternate/1", 0777)); @@ -197,4 +203,6 @@ void test_repo_config__read_no_configs(void) cl_assert(!git_path_exists("empty_standard_repo/.git/config")); cl_assert(!git_path_exists("alternate/3/.gitconfig")); + + git_futils_dirs_global_shutdown(); } diff --git a/tests-clar/repo/discover.c b/tests/repo/discover.c similarity index 100% rename from tests-clar/repo/discover.c rename to tests/repo/discover.c diff --git a/tests-clar/repo/getters.c b/tests/repo/getters.c similarity index 100% rename from tests-clar/repo/getters.c rename to tests/repo/getters.c diff --git a/tests-clar/repo/hashfile.c b/tests/repo/hashfile.c similarity index 100% rename from tests-clar/repo/hashfile.c rename to tests/repo/hashfile.c diff --git a/tests-clar/repo/head.c b/tests/repo/head.c similarity index 85% rename from tests-clar/repo/head.c rename to tests/repo/head.c index a9f5cfc58..5a55984bd 100644 --- a/tests-clar/repo/head.c +++ b/tests/repo/head.c @@ -32,20 +32,20 @@ void test_repo_head__head_detached(void) cl_assert_equal_i(false, git_repository_head_detached(repo)); } -void test_repo_head__head_orphan(void) +void test_repo_head__unborn_head(void) { git_reference *ref; cl_git_pass(git_repository_head_detached(repo)); - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); - cl_assert(git_repository_head_orphan(repo) == 1); + cl_assert(git_repository_head_unborn(repo) == 1); /* take the repo back to it's original state */ cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_orphan(repo) == 0); + cl_assert(git_repository_head_unborn(repo) == 0); git_reference_free(ref); } @@ -58,7 +58,7 @@ void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_ cl_assert_equal_i(false, git_repository_head_detached(repo)); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_head(&head, repo)); + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); } void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) @@ -163,20 +163,20 @@ void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void git_reference_free(head); } -void test_repo_head__detaching_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void) +void test_repo_head__detaching_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) { - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_detach_head(repo)); + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_detach_head(repo)); } -void test_repo_head__retrieving_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void) +void test_repo_head__retrieving_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) { git_reference *head; - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_head(&head, repo)); + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); } void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void) @@ -188,9 +188,9 @@ void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void) cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); } -void test_repo_head__can_tell_if_an_orphaned_head_is_detached(void) +void test_repo_head__can_tell_if_an_unborn_head_is_detached(void) { - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); cl_assert_equal_i(false, git_repository_head_detached(repo)); } diff --git a/tests-clar/repo/headtree.c b/tests/repo/headtree.c similarity index 80% rename from tests-clar/repo/headtree.c rename to tests/repo/headtree.c index 0e7fe93e5..e899ac399 100644 --- a/tests-clar/repo/headtree.c +++ b/tests/repo/headtree.c @@ -36,13 +36,13 @@ void test_repo_headtree__can_retrieve_the_root_tree_from_a_non_detached_head(voi cl_assert(git_oid_streq(git_tree_id(tree), "az")); } -void test_repo_headtree__when_head_is_orphaned_returns_EORPHANEDHEAD(void) +void test_repo_headtree__when_head_is_unborn_returns_EUNBORNBRANCH(void) { - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); - cl_assert_equal_i(true, git_repository_head_orphan(repo)); + cl_assert_equal_i(true, git_repository_head_unborn(repo)); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_head_tree(&tree, repo)); + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head_tree(&tree, repo)); } void test_repo_headtree__when_head_is_missing_returns_ENOTFOUND(void) diff --git a/tests-clar/repo/init.c b/tests/repo/init.c similarity index 74% rename from tests-clar/repo/init.c rename to tests/repo/init.c index 8cf73795f..aea383c64 100644 --- a/tests-clar/repo/init.c +++ b/tests/repo/init.c @@ -10,10 +10,17 @@ enum repo_mode { }; static git_repository *_repo = NULL; +static mode_t g_umask = 0; void test_repo_init__initialize(void) { _repo = NULL; + + /* load umask if not already loaded */ + if (!g_umask) { + g_umask = p_umask(022); + (void)p_umask(g_umask); + } } static void cleanup_repository(void *path) @@ -172,41 +179,32 @@ void test_repo_init__additional_templates(void) git_buf_free(&path); } -static void assert_config_entry_on_init_bytype(const char *config_key, int expected_value, bool is_bare) +static void assert_config_entry_on_init_bytype( + const char *config_key, int expected_value, bool is_bare) { git_config *config; - int current_value; - git_buf repo_path = GIT_BUF_INIT; + int error, current_value; + const char *repo_path = is_bare ? + "config_entry/test.bare.git" : "config_entry/test.non.bare.git"; cl_set_cleanup(&cleanup_repository, "config_entry"); - cl_git_pass(git_buf_puts(&repo_path, "config_entry/test.")); + cl_git_pass(git_repository_init(&_repo, repo_path, is_bare)); - if (!is_bare) - cl_git_pass(git_buf_puts(&repo_path, "non.")); - - cl_git_pass(git_buf_puts(&repo_path, "bare.git")); - - cl_git_pass(git_repository_init(&_repo, git_buf_cstr(&repo_path), is_bare)); - - git_buf_free(&repo_path); - - git_repository_config(&config, _repo); + cl_git_pass(git_repository_config(&config, _repo)); + error = git_config_get_bool(¤t_value, config, config_key); + git_config_free(config); if (expected_value >= 0) { - cl_git_pass(git_config_get_bool(¤t_value, config, config_key)); - + cl_assert_equal_i(0, error); cl_assert_equal_i(expected_value, current_value); } else { - int error = git_config_get_bool(¤t_value, config, config_key); - cl_assert_equal_i(expected_value, error); } - - git_config_free(config); } -static void assert_config_entry_on_init(const char *config_key, int expected_value) +static void assert_config_entry_on_init( + const char *config_key, int expected_value) { assert_config_entry_on_init_bytype(config_key, expected_value, true); git_repository_free(_repo); @@ -216,21 +214,36 @@ static void assert_config_entry_on_init(const char *config_key, int expected_val void test_repo_init__detect_filemode(void) { -#ifdef GIT_WIN32 - assert_config_entry_on_init("core.filemode", false); -#else - assert_config_entry_on_init("core.filemode", true); -#endif + assert_config_entry_on_init("core.filemode", cl_is_chmod_supported()); } -#define CASE_INSENSITIVE_FILESYSTEM (defined GIT_WIN32 || defined __APPLE__) - void test_repo_init__detect_ignorecase(void) { -#if CASE_INSENSITIVE_FILESYSTEM - assert_config_entry_on_init("core.ignorecase", true); + struct stat st; + bool found_without_match; + + cl_git_write2file("testCAPS", "whatever\n", 0, O_CREAT | O_WRONLY, 0666); + found_without_match = (p_stat("Testcaps", &st) == 0); + cl_must_pass(p_unlink("testCAPS")); + + assert_config_entry_on_init( + "core.ignorecase", found_without_match ? true : GIT_ENOTFOUND); +} + +void test_repo_init__detect_precompose_unicode_required(void) +{ +#ifdef GIT_USE_ICONV + char *composed = "ḱṷṓn", *decomposed = "ḱṷṓn"; + struct stat st; + bool found_with_nfd; + + cl_git_write2file(composed, "whatever\n", 0, O_CREAT | O_WRONLY, 0666); + found_with_nfd = (p_stat(decomposed, &st) == 0); + cl_must_pass(p_unlink(composed)); + + assert_config_entry_on_init("core.precomposeunicode", found_with_nfd); #else - assert_config_entry_on_init("core.ignorecase", GIT_ENOTFOUND); + assert_config_entry_on_init("core.precomposeunicode", GIT_ENOTFOUND); #endif } @@ -263,14 +276,7 @@ void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) void test_repo_init__reinit_overwrites_filemode(void) { - git_config *config; - int expected, current_value; - -#ifdef GIT_WIN32 - expected = false; -#else - expected = true; -#endif + int expected = cl_is_chmod_supported(), current_value; /* Init a new repo */ cl_set_cleanup(&cleanup_repository, "overwrite.git"); @@ -284,13 +290,10 @@ void test_repo_init__reinit_overwrites_filemode(void) /* Reinit the repository */ cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1)); - git_repository_config(&config, _repo); /* Ensure the "core.filemode" config value has been reset */ - cl_git_pass(git_config_get_bool(¤t_value, config, "core.filemode")); + current_value = cl_repo_get_bool(_repo, "core.filemode"); cl_assert_equal_i(expected, current_value); - - git_config_free(config); } void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) @@ -345,7 +348,10 @@ void test_repo_init__extended_1(void) cl_git_pass(git_path_lstat(git_repository_path(_repo), &st)); cl_assert(S_ISDIR(st.st_mode)); - cl_assert((S_ISGID & st.st_mode) == S_ISGID); + if (cl_is_chmod_supported()) + cl_assert((S_ISGID & st.st_mode) == S_ISGID); + else + cl_assert((S_ISGID & st.st_mode) == 0); cl_git_pass(git_reference_lookup(&ref, _repo, "HEAD")); cl_assert(git_reference_type(ref) == GIT_REF_SYMBOLIC); @@ -361,6 +367,8 @@ void test_repo_init__extended_1(void) cl_fixture_cleanup("root"); } +#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) + static void assert_hooks_match( const char *template_dir, const char *repo_dir, @@ -379,12 +387,18 @@ static void assert_hooks_match( cl_assert(expected_st.st_size == st.st_size); - if (!core_filemode) { - expected_st.st_mode = expected_st.st_mode & ~0111; - st.st_mode = st.st_mode & ~0111; - } + if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { + mode_t expected_mode = + GIT_MODE_TYPE(expected_st.st_mode) | + (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); - cl_assert_equal_i((int)expected_st.st_mode, (int)st.st_mode); + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expected_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + } + + cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); + } git_buf_free(&expected); git_buf_free(&actual); @@ -402,24 +416,19 @@ static void assert_mode_seems_okay( git_buf_free(&full); if (!core_filemode) { - expect_mode = expect_mode & ~0111; - st.st_mode = st.st_mode & ~0111; + CLEAR_FOR_CORE_FILEMODE(expect_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); expect_setgid = false; } - if (S_ISGID != 0) { - if (expect_setgid) - cl_assert((st.st_mode & S_ISGID) != 0); - else - cl_assert((st.st_mode & S_ISGID) == 0); - } + if (S_ISGID != 0) + cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); - if ((expect_mode & 0111) != 0) - cl_assert((st.st_mode & 0111) != 0); - else - cl_assert((st.st_mode & 0111) == 0); + cl_assert_equal_b( + GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); - cl_assert((expect_mode & 0170000) == (st.st_mode & 0170000)); + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); } void test_repo_init__extended_with_template(void) @@ -427,6 +436,7 @@ void test_repo_init__extended_with_template(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; cl_set_cleanup(&cleanup_repository, "templated.git"); @@ -450,13 +460,15 @@ void test_repo_init__extended_with_template(void) git_buf_free(&expected); git_buf_free(&actual); - assert_hooks_match( - cl_fixture("template"), git_repository_path(_repo), - "hooks/update.sample", true); + filemode = cl_repo_get_bool(_repo, "core.filemode"); assert_hooks_match( cl_fixture("template"), git_repository_path(_repo), - "hooks/link.sample", true); + "hooks/update.sample", filemode); + + assert_hooks_match( + cl_fixture("template"), git_repository_path(_repo), + "hooks/link.sample", filemode); } void test_repo_init__extended_with_template_and_shared_mode(void) @@ -464,7 +476,6 @@ 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; - git_config *config; int filemode = true; const char *repo_path = NULL; @@ -480,9 +491,7 @@ void test_repo_init__extended_with_template_and_shared_mode(void) cl_assert(!git_repository_is_bare(_repo)); cl_assert(!git__suffixcmp(git_repository_path(_repo), "/init_shared_from_tpl/.git/")); - cl_git_pass(git_repository_config(&config, _repo)); - cl_git_pass(git_config_get_bool(&filemode, config, "core.filemode")); - git_config_free(config); + filemode = cl_repo_get_bool(_repo, "core.filemode"); cl_git_pass(git_futils_readbuffer( &expected, cl_fixture("template/description"))); @@ -530,3 +539,68 @@ void test_repo_init__can_reinit_an_initialized_repository(void) git_repository_free(reinit); } + +void test_repo_init__init_with_initial_commit(void) +{ + git_index *index; + + cl_set_cleanup(&cleanup_repository, "committed"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&_repo, "committed", 0)); + + /* Init will be automatically created when requested for a new repo */ + cl_git_pass(git_repository_index(&index, _repo)); + + /* Create a file so we can commit it + * + * If you are writing code outside the test suite, you can create this + * file any way that you like, such as: + * FILE *fp = fopen("committed/file.txt", "w"); + * fputs("some stuff\n", fp); + * fclose(fp); + * We like to use the help functions because they do error detection + * in a way that's easily compatible with our test suite. + */ + cl_git_mkfile("committed/file.txt", "some stuff\n"); + + /* Add file to the index */ + cl_git_pass(git_index_add_bypath(index, "file.txt")); + cl_git_pass(git_index_write(index)); + + /* Intentionally not using cl_repo_commit_from_index here so this code + * can be used as an example of how an initial commit is typically + * made to a repository... + */ + + /* Make sure we're ready to use git_signature_default :-) */ + { + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + cl_git_pass(git_config_set_string(local, "user.name", "Test User")); + cl_git_pass(git_config_set_string(local, "user.email", "t@example.com")); + git_config_free(local); + git_config_free(cfg); + } + + /* Create a commit with the new contents of the index */ + { + git_signature *sig; + git_oid tree_id, commit_id; + git_tree *tree; + + cl_git_pass(git_signature_default(&sig, _repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + + cl_git_pass(git_commit_create_v( + &commit_id, _repo, "HEAD", sig, sig, + NULL, "First", tree, 0)); + + git_tree_free(tree); + git_signature_free(sig); + } + + git_index_free(index); +} diff --git a/tests-clar/repo/iterator.c b/tests/repo/iterator.c similarity index 96% rename from tests-clar/repo/iterator.c rename to tests/repo/iterator.c index 11a7d2a23..56b51852c 100644 --- a/tests-clar/repo/iterator.c +++ b/tests/repo/iterator.c @@ -906,6 +906,7 @@ void test_repo_iterator__fs2(void) static const char *expect_base[] = { "heads/br2", "heads/dir", + "heads/long-file-name", "heads/master", "heads/packed-test", "heads/subtrees", @@ -922,6 +923,40 @@ void test_repo_iterator__fs2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", 0, NULL, NULL)); - expect_iterator_items(i, 11, expect_base, 11, expect_base); + expect_iterator_items(i, 12, expect_base, 12, expect_base); + git_iterator_free(i); +} + +void test_repo_iterator__fs_preserves_error(void) +{ + git_iterator *i; + const git_index_entry *e; + + if (!cl_is_chmod_supported()) + return; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); + cl_git_mkfile("empty_standard_repo/r/a", "hello"); + cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); + cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); + cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); + cl_git_mkfile("empty_standard_repo/r/d", "final"); + + cl_git_pass(git_iterator_for_filesystem( + &i, "empty_standard_repo/r", 0, NULL, NULL)); + + cl_git_pass(git_iterator_advance(&e, i)); /* a */ + cl_git_fail(git_iterator_advance(&e, i)); /* b */ + cl_assert(giterr_last()); + cl_assert(giterr_last()->message != NULL); + /* skip 'c/' empty directory */ + cl_git_pass(git_iterator_advance(&e, i)); /* d */ + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); + + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); + git_iterator_free(i); } diff --git a/tests-clar/repo/message.c b/tests/repo/message.c similarity index 100% rename from tests-clar/repo/message.c rename to tests/repo/message.c diff --git a/tests-clar/repo/open.c b/tests/repo/open.c similarity index 82% rename from tests-clar/repo/open.c rename to tests/repo/open.c index 840858586..7cfe041c2 100644 --- a/tests-clar/repo/open.c +++ b/tests/repo/open.c @@ -69,14 +69,23 @@ void test_repo_open__open_with_discover(void) cl_fixture_cleanup("attr"); } +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_buf_joinpath(&path, dir, ".git")); + cl_git_rewritefile(path.ptr, linktext); + git_buf_free(&path); +} + void test_repo_open__gitlinked(void) { /* need to have both repo dir and workdir set up correctly */ git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); git_repository *repo2; - cl_must_pass(p_mkdir("alternate", 0777)); - cl_git_mkfile("alternate/.git", "gitdir: ../empty_standard_repo/.git"); + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); cl_git_pass(git_repository_open(&repo2, "alternate")); @@ -193,12 +202,11 @@ void test_repo_open__bad_gitlinks(void) cl_git_sandbox_init("attr"); - cl_git_pass(p_mkdir("alternate", 0777)); cl_git_pass(p_mkdir("invalid", 0777)); cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777)); for (scan = bad_links; *scan != NULL; scan++) { - cl_git_rewritefile("alternate/.git", *scan); + make_gitlink_dir("alternate", *scan); cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); } @@ -314,4 +322,55 @@ void test_repo_open__no_config(void) git_config_free(config); git_repository_free(repo); cl_fixture_cleanup("empty_standard_repo"); + + git_futils_dirs_global_shutdown(); +} + +void test_repo_open__force_bare(void) +{ + /* need to have both repo dir and workdir set up correctly */ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_repository *barerepo; + + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); + + cl_assert(!git_repository_is_bare(repo)); + + cl_git_pass(git_repository_open(&barerepo, "alternate")); + cl_assert(!git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(git_repository_open_bare( + &barerepo, "empty_standard_repo/.git")); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777)); + cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something"); + + cl_git_fail(git_repository_open_bare( + &barerepo, "empty_standard_repo/subdir")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("alternate/subdir", 0777)); + cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777)); + cl_git_mkfile("alternate/subdir/sub2/something.txt", "something"); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/subdir/sub2", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); } diff --git a/tests/repo/pathspec.c b/tests/repo/pathspec.c new file mode 100644 index 000000000..334066b67 --- /dev/null +++ b/tests/repo/pathspec.c @@ -0,0 +1,385 @@ +#include "clar_libgit2.h" +#include "git2/pathspec.h" + +static git_repository *g_repo; + +void test_repo_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_repo_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static char *str0[] = { "*_file", "new_file", "garbage" }; +static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" }; +static char *str2[] = { "staged_*" }; +static char *str3[] = { "!subdir", "*_file", "new_file" }; +static char *str4[] = { "*" }; +static char *str5[] = { "S*" }; + +void test_repo_pathspec__workdir0(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir1(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir2(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "staged_*" } */ + s.strings = str2; s.count = ARRAY_SIZE(str2); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir3(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "!subdir", "*_file", "new_file" } */ + s.strings = str3; s.count = ARRAY_SIZE(str3); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir4(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*" } */ + s.strings = str4; s.count = ARRAY_SIZE(str4); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("这", git_pathspec_match_list_entry(m, 12)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + + +void test_repo_pathspec__index0(void) +{ + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +} + +void test_repo_pathspec__index1(void) +{ + /* Currently the USE_CASE and IGNORE_CASE flags don't work on the + * index because the index sort order for the index iterator is + * set by the index itself. I think the correct fix is for the + * index not to embed a global sort order but to support traversal + * in either case sensitive or insensitive order in a stateless + * manner. + * + * Anyhow, as it is, there is no point in doing this test. + */ +#if 0 + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +#endif +} + +void test_repo_pathspec__tree0(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__tree5(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "S*" } */ + s.strings = str5; s.count = ARRAY_SIZE(str5); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__in_memory(void) +{ + static char *strings[] = { "one", "two*", "!three*", "*four" }; + git_strarray s = { strings, ARRAY_SIZE(strings) }; + git_pathspec *ps; + + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_assert(git_pathspec_matches_path(ps, 0, "one")); + cl_assert(!git_pathspec_matches_path(ps, 0, "ONE")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE")); + cl_assert(git_pathspec_matches_path(ps, 0, "two")); + cl_assert(git_pathspec_matches_path(ps, 0, "two.txt")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt")); + cl_assert(git_pathspec_matches_path(ps, 0, "anything.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four")); + + git_pathspec_free(ps); +} diff --git a/tests-clar/repo/repo_helpers.c b/tests/repo/repo_helpers.c similarity index 87% rename from tests-clar/repo/repo_helpers.c rename to tests/repo/repo_helpers.c index 74902e439..3d477ff42 100644 --- a/tests-clar/repo/repo_helpers.c +++ b/tests/repo/repo_helpers.c @@ -3,7 +3,7 @@ #include "repo_helpers.h" #include "posix.h" -void make_head_orphaned(git_repository* repo, const char *target) +void make_head_unborn(git_repository* repo, const char *target) { git_reference *head; diff --git a/tests-clar/repo/repo_helpers.h b/tests/repo/repo_helpers.h similarity index 62% rename from tests-clar/repo/repo_helpers.h rename to tests/repo/repo_helpers.h index 09b5cac84..6783d5701 100644 --- a/tests-clar/repo/repo_helpers.h +++ b/tests/repo/repo_helpers.h @@ -2,5 +2,5 @@ #define NON_EXISTING_HEAD "refs/heads/hide/and/seek" -extern void make_head_orphaned(git_repository* repo, const char *target); +extern void make_head_unborn(git_repository* repo, const char *target); extern void delete_head(git_repository* repo); diff --git a/tests-clar/repo/setters.c b/tests/repo/setters.c similarity index 100% rename from tests-clar/repo/setters.c rename to tests/repo/setters.c diff --git a/tests-clar/repo/shallow.c b/tests/repo/shallow.c similarity index 100% rename from tests-clar/repo/shallow.c rename to tests/repo/shallow.c diff --git a/tests-clar/repo/state.c b/tests/repo/state.c similarity index 100% rename from tests-clar/repo/state.c rename to tests/repo/state.c diff --git a/tests-clar/reset/default.c b/tests/reset/default.c similarity index 100% rename from tests-clar/reset/default.c rename to tests/reset/default.c diff --git a/tests-clar/reset/hard.c b/tests/reset/hard.c similarity index 100% rename from tests-clar/reset/hard.c rename to tests/reset/hard.c diff --git a/tests-clar/reset/mixed.c b/tests/reset/mixed.c similarity index 100% rename from tests-clar/reset/mixed.c rename to tests/reset/mixed.c diff --git a/tests-clar/reset/reset_helpers.c b/tests/reset/reset_helpers.c similarity index 100% rename from tests-clar/reset/reset_helpers.c rename to tests/reset/reset_helpers.c diff --git a/tests-clar/reset/reset_helpers.h b/tests/reset/reset_helpers.h similarity index 100% rename from tests-clar/reset/reset_helpers.h rename to tests/reset/reset_helpers.h diff --git a/tests-clar/reset/soft.c b/tests/reset/soft.c similarity index 94% rename from tests-clar/reset/soft.c rename to tests/reset/soft.c index 884697c91..bd6fcc205 100644 --- a/tests-clar/reset/soft.c +++ b/tests/reset/soft.c @@ -95,19 +95,19 @@ void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void) cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT)); } -void test_reset_soft__resetting_against_an_orphaned_head_repo_makes_the_head_no_longer_orphaned(void) +void test_reset_soft__resetting_against_an_unborn_head_repo_makes_the_head_no_longer_unborn(void) { git_reference *head; retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO); - make_head_orphaned(repo, NON_EXISTING_HEAD); + make_head_unborn(repo, NON_EXISTING_HEAD); - cl_assert_equal_i(true, git_repository_head_orphan(repo)); + cl_assert_equal_i(true, git_repository_head_unborn(repo)); cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT)); - cl_assert_equal_i(false, git_repository_head_orphan(repo)); + cl_assert_equal_i(false, git_repository_head_unborn(repo)); cl_git_pass(git_reference_lookup(&head, repo, NON_EXISTING_HEAD)); cl_assert_equal_i(0, git_oid_streq(git_reference_target(head), KNOWN_COMMIT_IN_BARE_REPO)); diff --git a/tests-clar/resources/.gitattributes b/tests/resources/.gitattributes similarity index 100% rename from tests-clar/resources/.gitattributes rename to tests/resources/.gitattributes diff --git a/tests-clar/resources/.gitignore b/tests/resources/.gitignore similarity index 100% rename from tests-clar/resources/.gitignore rename to tests/resources/.gitignore diff --git a/tests-clar/resources/attr/.gitted/HEAD b/tests/resources/attr/.gitted/HEAD similarity index 100% rename from tests-clar/resources/attr/.gitted/HEAD rename to tests/resources/attr/.gitted/HEAD diff --git a/tests-clar/resources/attr/.gitted/config b/tests/resources/attr/.gitted/config similarity index 100% rename from tests-clar/resources/attr/.gitted/config rename to tests/resources/attr/.gitted/config diff --git a/tests-clar/resources/attr/.gitted/description b/tests/resources/attr/.gitted/description similarity index 100% rename from tests-clar/resources/attr/.gitted/description rename to tests/resources/attr/.gitted/description diff --git a/tests-clar/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index similarity index 100% rename from tests-clar/resources/attr/.gitted/index rename to tests/resources/attr/.gitted/index diff --git a/tests-clar/resources/attr/.gitted/info/attributes b/tests/resources/attr/.gitted/info/attributes similarity index 100% rename from tests-clar/resources/attr/.gitted/info/attributes rename to tests/resources/attr/.gitted/info/attributes diff --git a/tests-clar/resources/attr/.gitted/info/exclude b/tests/resources/attr/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/attr/.gitted/info/exclude rename to tests/resources/attr/.gitted/info/exclude diff --git a/tests-clar/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/attr/.gitted/logs/HEAD rename to tests/resources/attr/.gitted/logs/HEAD diff --git a/tests-clar/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/attr/.gitted/logs/refs/heads/master rename to tests/resources/attr/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e b/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e rename to tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e diff --git a/tests-clar/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 b/tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 rename to tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 diff --git a/tests-clar/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 b/tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 rename to tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 diff --git a/tests-clar/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b b/tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b rename to tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b diff --git a/tests-clar/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b rename to tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b diff --git a/tests-clar/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a rename to tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a diff --git a/tests-clar/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 rename to tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 diff --git a/tests-clar/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 rename to tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 diff --git a/tests-clar/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a rename to tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a diff --git a/tests-clar/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 rename to tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 diff --git a/tests-clar/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 rename to tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 diff --git a/tests-clar/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 rename to tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 diff --git a/tests-clar/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d rename to tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d diff --git a/tests-clar/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 b/tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 rename to tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 diff --git a/tests-clar/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d rename to tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d diff --git a/tests-clar/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 b/tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 rename to tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 diff --git a/tests-clar/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 rename to tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 diff --git a/tests-clar/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 rename to tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 diff --git a/tests-clar/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 rename to tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 diff --git a/tests-clar/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da rename to tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da diff --git a/tests-clar/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd b/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd rename to tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd diff --git a/tests-clar/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 rename to tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 diff --git a/tests-clar/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 b/tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 rename to tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 diff --git a/tests-clar/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 b/tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 rename to tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 diff --git a/tests-clar/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 rename to tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 diff --git a/tests-clar/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 rename to tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 diff --git a/tests-clar/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 rename to tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 diff --git a/tests-clar/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 b/tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 rename to tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 diff --git a/tests-clar/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 rename to tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 diff --git a/tests-clar/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b b/tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b rename to tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b diff --git a/tests-clar/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 rename to tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 diff --git a/tests-clar/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 rename to tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 diff --git a/tests-clar/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba b/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba rename to tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba diff --git a/tests-clar/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04 b/tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04 rename to tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04 diff --git a/tests-clar/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 rename to tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 diff --git a/tests-clar/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c rename to tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c diff --git a/tests-clar/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d rename to tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d diff --git a/tests-clar/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 rename to tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 diff --git a/tests-clar/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e b/tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e rename to tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e diff --git a/tests-clar/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 rename to tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 diff --git a/tests-clar/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 rename to tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 diff --git a/tests-clar/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 rename to tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 diff --git a/tests-clar/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049 b/tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049 rename to tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049 diff --git a/tests-clar/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 rename to tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 diff --git a/tests-clar/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b b/tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b rename to tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b diff --git a/tests-clar/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53 b/tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53 rename to tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53 diff --git a/tests-clar/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 rename to tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 diff --git a/tests-clar/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 rename to tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 diff --git a/tests-clar/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 rename to tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 diff --git a/tests-clar/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc rename to tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc diff --git a/tests-clar/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b similarity index 100% rename from tests-clar/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b rename to tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b diff --git a/tests-clar/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/attr/.gitted/refs/heads/master rename to tests/resources/attr/.gitted/refs/heads/master diff --git a/tests-clar/resources/attr/attr0 b/tests/resources/attr/attr0 similarity index 100% rename from tests-clar/resources/attr/attr0 rename to tests/resources/attr/attr0 diff --git a/tests-clar/resources/attr/attr1 b/tests/resources/attr/attr1 similarity index 100% rename from tests-clar/resources/attr/attr1 rename to tests/resources/attr/attr1 diff --git a/tests-clar/resources/attr/attr2 b/tests/resources/attr/attr2 similarity index 100% rename from tests-clar/resources/attr/attr2 rename to tests/resources/attr/attr2 diff --git a/tests-clar/resources/attr/attr3 b/tests/resources/attr/attr3 similarity index 100% rename from tests-clar/resources/attr/attr3 rename to tests/resources/attr/attr3 diff --git a/tests-clar/resources/attr/binfile b/tests/resources/attr/binfile similarity index 100% rename from tests-clar/resources/attr/binfile rename to tests/resources/attr/binfile diff --git a/tests-clar/resources/attr/dir/file b/tests/resources/attr/dir/file similarity index 100% rename from tests-clar/resources/attr/dir/file rename to tests/resources/attr/dir/file diff --git a/tests-clar/resources/attr/file b/tests/resources/attr/file similarity index 100% rename from tests-clar/resources/attr/file rename to tests/resources/attr/file diff --git a/tests-clar/resources/attr/gitattributes b/tests/resources/attr/gitattributes similarity index 100% rename from tests-clar/resources/attr/gitattributes rename to tests/resources/attr/gitattributes diff --git a/tests-clar/resources/attr/gitignore b/tests/resources/attr/gitignore similarity index 100% rename from tests-clar/resources/attr/gitignore rename to tests/resources/attr/gitignore diff --git a/tests-clar/resources/attr/ign b/tests/resources/attr/ign similarity index 100% rename from tests-clar/resources/attr/ign rename to tests/resources/attr/ign diff --git a/tests-clar/resources/attr/macro_bad b/tests/resources/attr/macro_bad similarity index 100% rename from tests-clar/resources/attr/macro_bad rename to tests/resources/attr/macro_bad diff --git a/tests-clar/resources/attr/macro_test b/tests/resources/attr/macro_test similarity index 100% rename from tests-clar/resources/attr/macro_test rename to tests/resources/attr/macro_test diff --git a/tests-clar/resources/attr/root_test1 b/tests/resources/attr/root_test1 similarity index 100% rename from tests-clar/resources/attr/root_test1 rename to tests/resources/attr/root_test1 diff --git a/tests-clar/resources/attr/root_test2 b/tests/resources/attr/root_test2 similarity index 100% rename from tests-clar/resources/attr/root_test2 rename to tests/resources/attr/root_test2 diff --git a/tests-clar/resources/attr/root_test3 b/tests/resources/attr/root_test3 similarity index 100% rename from tests-clar/resources/attr/root_test3 rename to tests/resources/attr/root_test3 diff --git a/tests-clar/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt similarity index 100% rename from tests-clar/resources/attr/root_test4.txt rename to tests/resources/attr/root_test4.txt diff --git a/tests-clar/resources/attr/sub/.gitattributes b/tests/resources/attr/sub/.gitattributes similarity index 100% rename from tests-clar/resources/attr/sub/.gitattributes rename to tests/resources/attr/sub/.gitattributes diff --git a/tests-clar/resources/attr/sub/abc b/tests/resources/attr/sub/abc similarity index 100% rename from tests-clar/resources/attr/sub/abc rename to tests/resources/attr/sub/abc diff --git a/tests-clar/resources/attr/sub/dir/file b/tests/resources/attr/sub/dir/file similarity index 100% rename from tests-clar/resources/attr/sub/dir/file rename to tests/resources/attr/sub/dir/file diff --git a/tests-clar/resources/attr/sub/file b/tests/resources/attr/sub/file similarity index 100% rename from tests-clar/resources/attr/sub/file rename to tests/resources/attr/sub/file diff --git a/tests-clar/resources/attr/sub/ign/file b/tests/resources/attr/sub/ign/file similarity index 100% rename from tests-clar/resources/attr/sub/ign/file rename to tests/resources/attr/sub/ign/file diff --git a/tests-clar/resources/attr/sub/ign/sub/file b/tests/resources/attr/sub/ign/sub/file similarity index 100% rename from tests-clar/resources/attr/sub/ign/sub/file rename to tests/resources/attr/sub/ign/sub/file diff --git a/tests-clar/resources/attr/sub/sub/.gitattributes b/tests/resources/attr/sub/sub/.gitattributes similarity index 100% rename from tests-clar/resources/attr/sub/sub/.gitattributes rename to tests/resources/attr/sub/sub/.gitattributes diff --git a/tests-clar/resources/attr/sub/sub/dir b/tests/resources/attr/sub/sub/dir similarity index 100% rename from tests-clar/resources/attr/sub/sub/dir rename to tests/resources/attr/sub/sub/dir diff --git a/tests-clar/resources/attr/sub/sub/file b/tests/resources/attr/sub/sub/file similarity index 100% rename from tests-clar/resources/attr/sub/sub/file rename to tests/resources/attr/sub/sub/file diff --git a/tests-clar/resources/attr/sub/sub/subsub.txt b/tests/resources/attr/sub/sub/subsub.txt similarity index 100% rename from tests-clar/resources/attr/sub/sub/subsub.txt rename to tests/resources/attr/sub/sub/subsub.txt diff --git a/tests-clar/resources/attr/sub/subdir_test1 b/tests/resources/attr/sub/subdir_test1 similarity index 100% rename from tests-clar/resources/attr/sub/subdir_test1 rename to tests/resources/attr/sub/subdir_test1 diff --git a/tests-clar/resources/attr/sub/subdir_test2.txt b/tests/resources/attr/sub/subdir_test2.txt similarity index 100% rename from tests-clar/resources/attr/sub/subdir_test2.txt rename to tests/resources/attr/sub/subdir_test2.txt diff --git a/tests-clar/resources/attr_index/.gitted/HEAD b/tests/resources/attr_index/.gitted/HEAD similarity index 100% rename from tests-clar/resources/attr_index/.gitted/HEAD rename to tests/resources/attr_index/.gitted/HEAD diff --git a/tests-clar/resources/attr_index/.gitted/config b/tests/resources/attr_index/.gitted/config similarity index 100% rename from tests-clar/resources/attr_index/.gitted/config rename to tests/resources/attr_index/.gitted/config diff --git a/tests-clar/resources/attr_index/.gitted/description b/tests/resources/attr_index/.gitted/description similarity index 100% rename from tests-clar/resources/attr_index/.gitted/description rename to tests/resources/attr_index/.gitted/description diff --git a/tests-clar/resources/attr_index/.gitted/index b/tests/resources/attr_index/.gitted/index similarity index 100% rename from tests-clar/resources/attr_index/.gitted/index rename to tests/resources/attr_index/.gitted/index diff --git a/tests-clar/resources/attr_index/.gitted/info/exclude b/tests/resources/attr_index/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/attr_index/.gitted/info/exclude rename to tests/resources/attr_index/.gitted/info/exclude diff --git a/tests-clar/resources/attr_index/.gitted/info/refs b/tests/resources/attr_index/.gitted/info/refs similarity index 100% rename from tests-clar/resources/attr_index/.gitted/info/refs rename to tests/resources/attr_index/.gitted/info/refs diff --git a/tests-clar/resources/attr_index/.gitted/logs/HEAD b/tests/resources/attr_index/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/attr_index/.gitted/logs/HEAD rename to tests/resources/attr_index/.gitted/logs/HEAD diff --git a/tests-clar/resources/attr_index/.gitted/logs/refs/heads/master b/tests/resources/attr_index/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/attr_index/.gitted/logs/refs/heads/master rename to tests/resources/attr_index/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 b/tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 rename to tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 diff --git a/tests-clar/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b b/tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b rename to tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b diff --git a/tests-clar/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505 b/tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505 similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505 rename to tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505 diff --git a/tests-clar/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52 b/tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52 similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52 rename to tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52 diff --git a/tests-clar/resources/attr_index/.gitted/objects/info/packs b/tests/resources/attr_index/.gitted/objects/info/packs similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/info/packs rename to tests/resources/attr_index/.gitted/objects/info/packs diff --git a/tests-clar/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx rename to tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx diff --git a/tests-clar/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack similarity index 100% rename from tests-clar/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack rename to tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack diff --git a/tests-clar/resources/attr_index/.gitted/packed-refs b/tests/resources/attr_index/.gitted/packed-refs similarity index 100% rename from tests-clar/resources/attr_index/.gitted/packed-refs rename to tests/resources/attr_index/.gitted/packed-refs diff --git a/tests-clar/resources/attr_index/.gitted/refs/heads/master b/tests/resources/attr_index/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/attr_index/.gitted/refs/heads/master rename to tests/resources/attr_index/.gitted/refs/heads/master diff --git a/tests-clar/resources/attr_index/README.md b/tests/resources/attr_index/README.md similarity index 100% rename from tests-clar/resources/attr_index/README.md rename to tests/resources/attr_index/README.md diff --git a/tests-clar/resources/attr_index/README.txt b/tests/resources/attr_index/README.txt similarity index 100% rename from tests-clar/resources/attr_index/README.txt rename to tests/resources/attr_index/README.txt diff --git a/tests-clar/resources/attr_index/gitattributes b/tests/resources/attr_index/gitattributes similarity index 100% rename from tests-clar/resources/attr_index/gitattributes rename to tests/resources/attr_index/gitattributes diff --git a/tests-clar/resources/attr_index/sub/sub/.gitattributes b/tests/resources/attr_index/sub/sub/.gitattributes similarity index 100% rename from tests-clar/resources/attr_index/sub/sub/.gitattributes rename to tests/resources/attr_index/sub/sub/.gitattributes diff --git a/tests-clar/resources/attr_index/sub/sub/README.md b/tests/resources/attr_index/sub/sub/README.md similarity index 100% rename from tests-clar/resources/attr_index/sub/sub/README.md rename to tests/resources/attr_index/sub/sub/README.md diff --git a/tests-clar/resources/attr_index/sub/sub/README.txt b/tests/resources/attr_index/sub/sub/README.txt similarity index 100% rename from tests-clar/resources/attr_index/sub/sub/README.txt rename to tests/resources/attr_index/sub/sub/README.txt diff --git a/tests/resources/bad.index b/tests/resources/bad.index new file mode 100644 index 000000000..53746549f Binary files /dev/null and b/tests/resources/bad.index differ diff --git a/tests-clar/resources/bad_tag.git/HEAD b/tests/resources/bad_tag.git/HEAD similarity index 100% rename from tests-clar/resources/bad_tag.git/HEAD rename to tests/resources/bad_tag.git/HEAD diff --git a/tests-clar/resources/bad_tag.git/config b/tests/resources/bad_tag.git/config similarity index 100% rename from tests-clar/resources/bad_tag.git/config rename to tests/resources/bad_tag.git/config diff --git a/tests-clar/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx similarity index 100% rename from tests-clar/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx rename to tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx diff --git a/tests-clar/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack similarity index 100% rename from tests-clar/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack rename to tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack diff --git a/tests-clar/resources/bad_tag.git/packed-refs b/tests/resources/bad_tag.git/packed-refs similarity index 100% rename from tests-clar/resources/bad_tag.git/packed-refs rename to tests/resources/bad_tag.git/packed-refs diff --git a/tests-clar/resources/bad_tag.git/refs/dummy-marker.txt b/tests/resources/bad_tag.git/refs/dummy-marker.txt similarity index 100% rename from tests-clar/resources/bad_tag.git/refs/dummy-marker.txt rename to tests/resources/bad_tag.git/refs/dummy-marker.txt diff --git a/tests-clar/resources/big.index b/tests/resources/big.index similarity index 100% rename from tests-clar/resources/big.index rename to tests/resources/big.index diff --git a/tests-clar/resources/binaryunicode/.gitted/HEAD b/tests/resources/binaryunicode/.gitted/HEAD similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/HEAD rename to tests/resources/binaryunicode/.gitted/HEAD diff --git a/tests-clar/resources/binaryunicode/.gitted/config b/tests/resources/binaryunicode/.gitted/config similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/config rename to tests/resources/binaryunicode/.gitted/config diff --git a/tests-clar/resources/binaryunicode/.gitted/description b/tests/resources/binaryunicode/.gitted/description similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/description rename to tests/resources/binaryunicode/.gitted/description diff --git a/tests-clar/resources/binaryunicode/.gitted/index b/tests/resources/binaryunicode/.gitted/index similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/index rename to tests/resources/binaryunicode/.gitted/index diff --git a/tests-clar/resources/binaryunicode/.gitted/info/exclude b/tests/resources/binaryunicode/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/info/exclude rename to tests/resources/binaryunicode/.gitted/info/exclude diff --git a/tests-clar/resources/binaryunicode/.gitted/info/refs b/tests/resources/binaryunicode/.gitted/info/refs similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/info/refs rename to tests/resources/binaryunicode/.gitted/info/refs diff --git a/tests-clar/resources/binaryunicode/.gitted/objects/info/packs b/tests/resources/binaryunicode/.gitted/objects/info/packs similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/objects/info/packs rename to tests/resources/binaryunicode/.gitted/objects/info/packs diff --git a/tests-clar/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx rename to tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx diff --git a/tests-clar/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack rename to tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack diff --git a/tests-clar/resources/binaryunicode/.gitted/refs/heads/branch1 b/tests/resources/binaryunicode/.gitted/refs/heads/branch1 similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/refs/heads/branch1 rename to tests/resources/binaryunicode/.gitted/refs/heads/branch1 diff --git a/tests-clar/resources/binaryunicode/.gitted/refs/heads/branch2 b/tests/resources/binaryunicode/.gitted/refs/heads/branch2 similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/refs/heads/branch2 rename to tests/resources/binaryunicode/.gitted/refs/heads/branch2 diff --git a/tests-clar/resources/binaryunicode/.gitted/refs/heads/master b/tests/resources/binaryunicode/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/binaryunicode/.gitted/refs/heads/master rename to tests/resources/binaryunicode/.gitted/refs/heads/master diff --git a/tests-clar/resources/binaryunicode/file.txt b/tests/resources/binaryunicode/file.txt similarity index 100% rename from tests-clar/resources/binaryunicode/file.txt rename to tests/resources/binaryunicode/file.txt diff --git a/tests-clar/resources/crlf/.gitted/HEAD b/tests/resources/blametest.git/HEAD similarity index 100% rename from tests-clar/resources/crlf/.gitted/HEAD rename to tests/resources/blametest.git/HEAD diff --git a/tests-clar/resources/twowaymerge.git/config b/tests/resources/blametest.git/config similarity index 100% rename from tests-clar/resources/twowaymerge.git/config rename to tests/resources/blametest.git/config diff --git a/tests-clar/resources/deprecated-mode.git/description b/tests/resources/blametest.git/description similarity index 100% rename from tests-clar/resources/deprecated-mode.git/description rename to tests/resources/blametest.git/description diff --git a/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448 b/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448 new file mode 100644 index 000000000..90331cef9 Binary files /dev/null and b/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448 differ diff --git a/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b b/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b new file mode 100644 index 000000000..71890274a Binary files /dev/null and b/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b differ diff --git a/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a b/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a new file mode 100644 index 000000000..e66430637 Binary files /dev/null and b/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a differ diff --git a/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a b/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a new file mode 100644 index 000000000..7da4cf5d4 Binary files /dev/null and b/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a differ diff --git a/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a b/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a new file mode 100644 index 000000000..5f41f2936 Binary files /dev/null and b/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a differ diff --git a/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36 b/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36 new file mode 100644 index 000000000..c6c285eeb Binary files /dev/null and b/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36 differ diff --git a/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc b/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc new file mode 100644 index 000000000..9d8f60531 Binary files /dev/null and b/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc differ diff --git a/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a b/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a new file mode 100644 index 000000000..d716e3b03 Binary files /dev/null and b/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a differ diff --git a/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c b/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c new file mode 100644 index 000000000..12407f662 Binary files /dev/null and b/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c differ diff --git a/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9 b/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9 new file mode 100644 index 000000000..bc13badb1 Binary files /dev/null and b/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9 differ diff --git a/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2 b/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2 new file mode 100644 index 000000000..caf5cc1e3 Binary files /dev/null and b/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2 differ diff --git a/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d b/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d new file mode 100644 index 000000000..d4f9ccaca Binary files /dev/null and b/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d differ diff --git a/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5 b/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5 new file mode 100644 index 000000000..a428fd63b Binary files /dev/null and b/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5 differ diff --git a/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136 b/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136 new file mode 100644 index 000000000..4e6ad159e Binary files /dev/null and b/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136 differ diff --git a/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3 b/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3 new file mode 100644 index 000000000..2048e81eb Binary files /dev/null and b/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3 differ diff --git a/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640 b/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640 new file mode 100644 index 000000000..926c4bbb0 Binary files /dev/null and b/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640 differ diff --git a/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda b/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda new file mode 100644 index 000000000..e9e13833f Binary files /dev/null and b/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda differ diff --git a/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243 b/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243 new file mode 100644 index 000000000..7e5586c2b Binary files /dev/null and b/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243 differ diff --git a/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1 b/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1 new file mode 100644 index 000000000..d021ccfde Binary files /dev/null and b/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1 differ diff --git a/tests/resources/blametest.git/refs/heads/master b/tests/resources/blametest.git/refs/heads/master new file mode 100644 index 000000000..b763025d8 Binary files /dev/null and b/tests/resources/blametest.git/refs/heads/master differ diff --git a/tests-clar/resources/config/.gitconfig b/tests/resources/config/.gitconfig similarity index 100% rename from tests-clar/resources/config/.gitconfig rename to tests/resources/config/.gitconfig diff --git a/tests/resources/config/config-include b/tests/resources/config/config-include new file mode 100644 index 000000000..6b5e79de7 Binary files /dev/null and b/tests/resources/config/config-include differ diff --git a/tests/resources/config/config-included b/tests/resources/config/config-included new file mode 100644 index 000000000..089ca08a7 Binary files /dev/null and b/tests/resources/config/config-included differ diff --git a/tests-clar/resources/config/config0 b/tests/resources/config/config0 similarity index 100% rename from tests-clar/resources/config/config0 rename to tests/resources/config/config0 diff --git a/tests-clar/resources/config/config1 b/tests/resources/config/config1 similarity index 100% rename from tests-clar/resources/config/config1 rename to tests/resources/config/config1 diff --git a/tests-clar/resources/config/config10 b/tests/resources/config/config10 similarity index 100% rename from tests-clar/resources/config/config10 rename to tests/resources/config/config10 diff --git a/tests-clar/resources/config/config11 b/tests/resources/config/config11 similarity index 83% rename from tests-clar/resources/config/config11 rename to tests/resources/config/config11 index 7331862a5..1d8a74470 100644 Binary files a/tests-clar/resources/config/config11 and b/tests/resources/config/config11 differ diff --git a/tests-clar/resources/config/config12 b/tests/resources/config/config12 similarity index 100% rename from tests-clar/resources/config/config12 rename to tests/resources/config/config12 diff --git a/tests-clar/resources/config/config13 b/tests/resources/config/config13 similarity index 100% rename from tests-clar/resources/config/config13 rename to tests/resources/config/config13 diff --git a/tests-clar/resources/config/config14 b/tests/resources/config/config14 similarity index 100% rename from tests-clar/resources/config/config14 rename to tests/resources/config/config14 diff --git a/tests-clar/resources/config/config15 b/tests/resources/config/config15 similarity index 100% rename from tests-clar/resources/config/config15 rename to tests/resources/config/config15 diff --git a/tests-clar/resources/config/config16 b/tests/resources/config/config16 similarity index 100% rename from tests-clar/resources/config/config16 rename to tests/resources/config/config16 diff --git a/tests-clar/resources/config/config17 b/tests/resources/config/config17 similarity index 100% rename from tests-clar/resources/config/config17 rename to tests/resources/config/config17 diff --git a/tests-clar/resources/config/config18 b/tests/resources/config/config18 similarity index 100% rename from tests-clar/resources/config/config18 rename to tests/resources/config/config18 diff --git a/tests-clar/resources/config/config19 b/tests/resources/config/config19 similarity index 100% rename from tests-clar/resources/config/config19 rename to tests/resources/config/config19 diff --git a/tests-clar/resources/config/config2 b/tests/resources/config/config2 similarity index 100% rename from tests-clar/resources/config/config2 rename to tests/resources/config/config2 diff --git a/tests/resources/config/config20 b/tests/resources/config/config20 new file mode 100644 index 000000000..8f0f12c4c Binary files /dev/null and b/tests/resources/config/config20 differ diff --git a/tests-clar/resources/config/config3 b/tests/resources/config/config3 similarity index 100% rename from tests-clar/resources/config/config3 rename to tests/resources/config/config3 diff --git a/tests-clar/resources/config/config4 b/tests/resources/config/config4 similarity index 100% rename from tests-clar/resources/config/config4 rename to tests/resources/config/config4 diff --git a/tests-clar/resources/config/config5 b/tests/resources/config/config5 similarity index 100% rename from tests-clar/resources/config/config5 rename to tests/resources/config/config5 diff --git a/tests-clar/resources/config/config6 b/tests/resources/config/config6 similarity index 100% rename from tests-clar/resources/config/config6 rename to tests/resources/config/config6 diff --git a/tests-clar/resources/config/config7 b/tests/resources/config/config7 similarity index 100% rename from tests-clar/resources/config/config7 rename to tests/resources/config/config7 diff --git a/tests-clar/resources/config/config8 b/tests/resources/config/config8 similarity index 100% rename from tests-clar/resources/config/config8 rename to tests/resources/config/config8 diff --git a/tests-clar/resources/config/config9 b/tests/resources/config/config9 similarity index 100% rename from tests-clar/resources/config/config9 rename to tests/resources/config/config9 diff --git a/tests-clar/resources/deprecated-mode.git/HEAD b/tests/resources/crlf/.gitted/HEAD similarity index 100% rename from tests-clar/resources/deprecated-mode.git/HEAD rename to tests/resources/crlf/.gitted/HEAD diff --git a/tests-clar/resources/crlf/.gitted/config b/tests/resources/crlf/.gitted/config similarity index 100% rename from tests-clar/resources/crlf/.gitted/config rename to tests/resources/crlf/.gitted/config diff --git a/tests-clar/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c b/tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c rename to tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c diff --git a/tests-clar/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 b/tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 rename to tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 diff --git a/tests-clar/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f b/tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f rename to tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f diff --git a/tests-clar/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 b/tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 rename to tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 diff --git a/tests-clar/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb b/tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb rename to tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb diff --git a/tests-clar/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6 b/tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6 rename to tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6 diff --git a/tests-clar/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a b/tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a rename to tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a diff --git a/tests-clar/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 b/tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 rename to tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 diff --git a/tests-clar/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 b/tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 rename to tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 diff --git a/tests-clar/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e b/tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e rename to tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e diff --git a/tests-clar/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c b/tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c rename to tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c diff --git a/tests-clar/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 b/tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 rename to tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 diff --git a/tests-clar/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f b/tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f similarity index 100% rename from tests-clar/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f rename to tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f diff --git a/tests-clar/resources/crlf/.gitted/refs/heads/master b/tests/resources/crlf/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/crlf/.gitted/refs/heads/master rename to tests/resources/crlf/.gitted/refs/heads/master diff --git a/tests-clar/resources/crlf/.gitted/refs/heads/utf8 b/tests/resources/crlf/.gitted/refs/heads/utf8 similarity index 100% rename from tests-clar/resources/crlf/.gitted/refs/heads/utf8 rename to tests/resources/crlf/.gitted/refs/heads/utf8 diff --git a/tests-clar/resources/diff/.gitted/HEAD b/tests/resources/deprecated-mode.git/HEAD similarity index 100% rename from tests-clar/resources/diff/.gitted/HEAD rename to tests/resources/deprecated-mode.git/HEAD diff --git a/tests-clar/resources/deprecated-mode.git/config b/tests/resources/deprecated-mode.git/config similarity index 100% rename from tests-clar/resources/deprecated-mode.git/config rename to tests/resources/deprecated-mode.git/config diff --git a/tests-clar/resources/diff/.gitted/description b/tests/resources/deprecated-mode.git/description similarity index 100% rename from tests-clar/resources/diff/.gitted/description rename to tests/resources/deprecated-mode.git/description diff --git a/tests-clar/resources/deprecated-mode.git/index b/tests/resources/deprecated-mode.git/index similarity index 100% rename from tests-clar/resources/deprecated-mode.git/index rename to tests/resources/deprecated-mode.git/index diff --git a/tests-clar/resources/deprecated-mode.git/info/exclude b/tests/resources/deprecated-mode.git/info/exclude similarity index 100% rename from tests-clar/resources/deprecated-mode.git/info/exclude rename to tests/resources/deprecated-mode.git/info/exclude diff --git a/tests-clar/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff b/tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff similarity index 100% rename from tests-clar/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff rename to tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff diff --git a/tests-clar/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7 b/tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7 similarity index 100% rename from tests-clar/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7 rename to tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7 diff --git a/tests-clar/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e b/tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e similarity index 100% rename from tests-clar/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e rename to tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e diff --git a/tests-clar/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021 b/tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021 similarity index 100% rename from tests-clar/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021 rename to tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021 diff --git a/tests-clar/resources/deprecated-mode.git/refs/heads/master b/tests/resources/deprecated-mode.git/refs/heads/master similarity index 100% rename from tests-clar/resources/deprecated-mode.git/refs/heads/master rename to tests/resources/deprecated-mode.git/refs/heads/master diff --git a/tests-clar/resources/duplicate.git/HEAD b/tests/resources/diff/.gitted/HEAD similarity index 100% rename from tests-clar/resources/duplicate.git/HEAD rename to tests/resources/diff/.gitted/HEAD diff --git a/tests-clar/resources/diff/.gitted/config b/tests/resources/diff/.gitted/config similarity index 100% rename from tests-clar/resources/diff/.gitted/config rename to tests/resources/diff/.gitted/config diff --git a/tests-clar/resources/duplicate.git/description b/tests/resources/diff/.gitted/description similarity index 100% rename from tests-clar/resources/duplicate.git/description rename to tests/resources/diff/.gitted/description diff --git a/tests-clar/resources/diff/.gitted/index b/tests/resources/diff/.gitted/index similarity index 100% rename from tests-clar/resources/diff/.gitted/index rename to tests/resources/diff/.gitted/index diff --git a/tests-clar/resources/diff/.gitted/info/exclude b/tests/resources/diff/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/diff/.gitted/info/exclude rename to tests/resources/diff/.gitted/info/exclude diff --git a/tests-clar/resources/diff/.gitted/logs/HEAD b/tests/resources/diff/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/diff/.gitted/logs/HEAD rename to tests/resources/diff/.gitted/logs/HEAD diff --git a/tests-clar/resources/diff/.gitted/logs/refs/heads/master b/tests/resources/diff/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/diff/.gitted/logs/refs/heads/master rename to tests/resources/diff/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 b/tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 rename to tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 diff --git a/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 b/tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 rename to tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 diff --git a/tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 b/tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 rename to tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 diff --git a/tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 b/tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 rename to tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 diff --git a/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 b/tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 rename to tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 diff --git a/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c b/tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c rename to tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c diff --git a/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 b/tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 rename to tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 diff --git a/tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 b/tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 similarity index 100% rename from tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 rename to tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 diff --git a/tests-clar/resources/diff/.gitted/refs/heads/master b/tests/resources/diff/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/diff/.gitted/refs/heads/master rename to tests/resources/diff/.gitted/refs/heads/master diff --git a/tests-clar/resources/diff/another.txt b/tests/resources/diff/another.txt similarity index 100% rename from tests-clar/resources/diff/another.txt rename to tests/resources/diff/another.txt diff --git a/tests-clar/resources/diff/readme.txt b/tests/resources/diff/readme.txt similarity index 100% rename from tests-clar/resources/diff/readme.txt rename to tests/resources/diff/readme.txt diff --git a/tests-clar/resources/duplicate.git/COMMIT_EDITMSG b/tests/resources/duplicate.git/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/duplicate.git/COMMIT_EDITMSG rename to tests/resources/duplicate.git/COMMIT_EDITMSG diff --git a/tests-clar/resources/empty_bare.git/HEAD b/tests/resources/duplicate.git/HEAD similarity index 100% rename from tests-clar/resources/empty_bare.git/HEAD rename to tests/resources/duplicate.git/HEAD diff --git a/tests-clar/resources/short_tag.git/config b/tests/resources/duplicate.git/config similarity index 100% rename from tests-clar/resources/short_tag.git/config rename to tests/resources/duplicate.git/config diff --git a/tests-clar/resources/empty_bare.git/description b/tests/resources/duplicate.git/description similarity index 100% rename from tests-clar/resources/empty_bare.git/description rename to tests/resources/duplicate.git/description diff --git a/tests-clar/resources/duplicate.git/index b/tests/resources/duplicate.git/index similarity index 100% rename from tests-clar/resources/duplicate.git/index rename to tests/resources/duplicate.git/index diff --git a/tests-clar/resources/duplicate.git/info/exclude b/tests/resources/duplicate.git/info/exclude similarity index 100% rename from tests-clar/resources/duplicate.git/info/exclude rename to tests/resources/duplicate.git/info/exclude diff --git a/tests-clar/resources/duplicate.git/info/refs b/tests/resources/duplicate.git/info/refs similarity index 100% rename from tests-clar/resources/duplicate.git/info/refs rename to tests/resources/duplicate.git/info/refs diff --git a/tests-clar/resources/duplicate.git/logs/HEAD b/tests/resources/duplicate.git/logs/HEAD similarity index 100% rename from tests-clar/resources/duplicate.git/logs/HEAD rename to tests/resources/duplicate.git/logs/HEAD diff --git a/tests-clar/resources/duplicate.git/logs/refs/heads/master b/tests/resources/duplicate.git/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/duplicate.git/logs/refs/heads/master rename to tests/resources/duplicate.git/logs/refs/heads/master diff --git a/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea b/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea new file mode 100644 index 000000000..47c2a631a Binary files /dev/null and b/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea differ diff --git a/tests-clar/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a similarity index 100% rename from tests-clar/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a rename to tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a diff --git a/tests-clar/resources/duplicate.git/objects/info/packs b/tests/resources/duplicate.git/objects/info/packs similarity index 50% rename from tests-clar/resources/duplicate.git/objects/info/packs rename to tests/resources/duplicate.git/objects/info/packs index 3696a7d36..d0fdf905e 100644 Binary files a/tests-clar/resources/duplicate.git/objects/info/packs and b/tests/resources/duplicate.git/objects/info/packs differ diff --git a/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx new file mode 100644 index 000000000..acbed82b6 Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx differ diff --git a/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack new file mode 100644 index 000000000..652b0c91f Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack differ diff --git a/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx new file mode 100644 index 000000000..fff685554 Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx differ diff --git a/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack new file mode 100644 index 000000000..e3e5f0e09 Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack differ diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx similarity index 100% rename from tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx rename to tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack similarity index 100% rename from tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack rename to tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack diff --git a/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx new file mode 100644 index 000000000..9f78f6e0f Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx differ diff --git a/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack new file mode 100644 index 000000000..d1dd3b61a Binary files /dev/null and b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack differ diff --git a/tests-clar/resources/duplicate.git/packed-refs b/tests/resources/duplicate.git/packed-refs similarity index 100% rename from tests-clar/resources/duplicate.git/packed-refs rename to tests/resources/duplicate.git/packed-refs diff --git a/tests/resources/duplicate.git/refs/heads/dummy-marker.txt b/tests/resources/duplicate.git/refs/heads/dummy-marker.txt new file mode 100644 index 000000000..421376db9 Binary files /dev/null and b/tests/resources/duplicate.git/refs/heads/dummy-marker.txt differ diff --git a/tests-clar/resources/empty_standard_repo/.gitted/HEAD b/tests/resources/empty_bare.git/HEAD similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/HEAD rename to tests/resources/empty_bare.git/HEAD diff --git a/tests-clar/resources/empty_bare.git/config b/tests/resources/empty_bare.git/config similarity index 100% rename from tests-clar/resources/empty_bare.git/config rename to tests/resources/empty_bare.git/config diff --git a/tests-clar/resources/empty_standard_repo/.gitted/description b/tests/resources/empty_bare.git/description similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/description rename to tests/resources/empty_bare.git/description diff --git a/tests-clar/resources/empty_bare.git/info/exclude b/tests/resources/empty_bare.git/info/exclude similarity index 100% rename from tests-clar/resources/empty_bare.git/info/exclude rename to tests/resources/empty_bare.git/info/exclude diff --git a/tests-clar/resources/empty_bare.git/objects/info/dummy-marker.txt b/tests/resources/empty_bare.git/objects/info/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_bare.git/objects/info/dummy-marker.txt rename to tests/resources/empty_bare.git/objects/info/dummy-marker.txt diff --git a/tests-clar/resources/empty_bare.git/objects/pack/dummy-marker.txt b/tests/resources/empty_bare.git/objects/pack/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_bare.git/objects/pack/dummy-marker.txt rename to tests/resources/empty_bare.git/objects/pack/dummy-marker.txt diff --git a/tests-clar/resources/empty_bare.git/refs/heads/dummy-marker.txt b/tests/resources/empty_bare.git/refs/heads/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_bare.git/refs/heads/dummy-marker.txt rename to tests/resources/empty_bare.git/refs/heads/dummy-marker.txt diff --git a/tests-clar/resources/empty_bare.git/refs/tags/dummy-marker.txt b/tests/resources/empty_bare.git/refs/tags/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_bare.git/refs/tags/dummy-marker.txt rename to tests/resources/empty_bare.git/refs/tags/dummy-marker.txt diff --git a/tests-clar/resources/filemodes/.gitted/HEAD b/tests/resources/empty_standard_repo/.gitted/HEAD similarity index 100% rename from tests-clar/resources/filemodes/.gitted/HEAD rename to tests/resources/empty_standard_repo/.gitted/HEAD diff --git a/tests-clar/resources/empty_standard_repo/.gitted/config b/tests/resources/empty_standard_repo/.gitted/config similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/config rename to tests/resources/empty_standard_repo/.gitted/config diff --git a/tests-clar/resources/filemodes/.gitted/description b/tests/resources/empty_standard_repo/.gitted/description similarity index 100% rename from tests-clar/resources/filemodes/.gitted/description rename to tests/resources/empty_standard_repo/.gitted/description diff --git a/tests-clar/resources/empty_standard_repo/.gitted/info/exclude b/tests/resources/empty_standard_repo/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/info/exclude rename to tests/resources/empty_standard_repo/.gitted/info/exclude diff --git a/tests-clar/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt b/tests/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt rename to tests/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt diff --git a/tests-clar/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt b/tests/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt rename to tests/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt diff --git a/tests-clar/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt b/tests/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt rename to tests/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt diff --git a/tests-clar/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt b/tests/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt similarity index 100% rename from tests-clar/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt rename to tests/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt diff --git a/tests-clar/resources/icase/.gitted/HEAD b/tests/resources/filemodes/.gitted/HEAD similarity index 100% rename from tests-clar/resources/icase/.gitted/HEAD rename to tests/resources/filemodes/.gitted/HEAD diff --git a/tests-clar/resources/filemodes/.gitted/config b/tests/resources/filemodes/.gitted/config similarity index 100% rename from tests-clar/resources/filemodes/.gitted/config rename to tests/resources/filemodes/.gitted/config diff --git a/tests-clar/resources/icase/.gitted/description b/tests/resources/filemodes/.gitted/description similarity index 100% rename from tests-clar/resources/icase/.gitted/description rename to tests/resources/filemodes/.gitted/description diff --git a/tests-clar/resources/filemodes/.gitted/index b/tests/resources/filemodes/.gitted/index similarity index 100% rename from tests-clar/resources/filemodes/.gitted/index rename to tests/resources/filemodes/.gitted/index diff --git a/tests-clar/resources/filemodes/.gitted/info/exclude b/tests/resources/filemodes/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/filemodes/.gitted/info/exclude rename to tests/resources/filemodes/.gitted/info/exclude diff --git a/tests-clar/resources/filemodes/.gitted/logs/HEAD b/tests/resources/filemodes/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/filemodes/.gitted/logs/HEAD rename to tests/resources/filemodes/.gitted/logs/HEAD diff --git a/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master b/tests/resources/filemodes/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/filemodes/.gitted/logs/refs/heads/master rename to tests/resources/filemodes/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a b/tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a similarity index 100% rename from tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a rename to tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a diff --git a/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 b/tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 similarity index 100% rename from tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 rename to tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 diff --git a/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 b/tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 similarity index 100% rename from tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 rename to tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 diff --git a/tests-clar/resources/filemodes/.gitted/refs/heads/master b/tests/resources/filemodes/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/filemodes/.gitted/refs/heads/master rename to tests/resources/filemodes/.gitted/refs/heads/master diff --git a/tests-clar/resources/filemodes/exec_off b/tests/resources/filemodes/exec_off similarity index 100% rename from tests-clar/resources/filemodes/exec_off rename to tests/resources/filemodes/exec_off diff --git a/tests-clar/resources/filemodes/exec_off2on_staged b/tests/resources/filemodes/exec_off2on_staged similarity index 100% rename from tests-clar/resources/filemodes/exec_off2on_staged rename to tests/resources/filemodes/exec_off2on_staged diff --git a/tests-clar/resources/filemodes/exec_off2on_workdir b/tests/resources/filemodes/exec_off2on_workdir similarity index 100% rename from tests-clar/resources/filemodes/exec_off2on_workdir rename to tests/resources/filemodes/exec_off2on_workdir diff --git a/tests-clar/resources/filemodes/exec_off_untracked b/tests/resources/filemodes/exec_off_untracked similarity index 100% rename from tests-clar/resources/filemodes/exec_off_untracked rename to tests/resources/filemodes/exec_off_untracked diff --git a/tests-clar/resources/filemodes/exec_on b/tests/resources/filemodes/exec_on similarity index 100% rename from tests-clar/resources/filemodes/exec_on rename to tests/resources/filemodes/exec_on diff --git a/tests-clar/resources/filemodes/exec_on2off_staged b/tests/resources/filemodes/exec_on2off_staged similarity index 100% rename from tests-clar/resources/filemodes/exec_on2off_staged rename to tests/resources/filemodes/exec_on2off_staged diff --git a/tests-clar/resources/filemodes/exec_on2off_workdir b/tests/resources/filemodes/exec_on2off_workdir similarity index 100% rename from tests-clar/resources/filemodes/exec_on2off_workdir rename to tests/resources/filemodes/exec_on2off_workdir diff --git a/tests-clar/resources/filemodes/exec_on_untracked b/tests/resources/filemodes/exec_on_untracked similarity index 100% rename from tests-clar/resources/filemodes/exec_on_untracked rename to tests/resources/filemodes/exec_on_untracked diff --git a/tests-clar/resources/gitgit.index b/tests/resources/gitgit.index similarity index 100% rename from tests-clar/resources/gitgit.index rename to tests/resources/gitgit.index diff --git a/tests-clar/resources/issue_1397/.gitted/HEAD b/tests/resources/icase/.gitted/HEAD similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/HEAD rename to tests/resources/icase/.gitted/HEAD diff --git a/tests-clar/resources/icase/.gitted/config b/tests/resources/icase/.gitted/config similarity index 100% rename from tests-clar/resources/icase/.gitted/config rename to tests/resources/icase/.gitted/config diff --git a/tests-clar/resources/issue_592b/.gitted/description b/tests/resources/icase/.gitted/description similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/description rename to tests/resources/icase/.gitted/description diff --git a/tests-clar/resources/icase/.gitted/index b/tests/resources/icase/.gitted/index similarity index 100% rename from tests-clar/resources/icase/.gitted/index rename to tests/resources/icase/.gitted/index diff --git a/tests-clar/resources/icase/.gitted/info/exclude b/tests/resources/icase/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/icase/.gitted/info/exclude rename to tests/resources/icase/.gitted/info/exclude diff --git a/tests-clar/resources/icase/.gitted/logs/HEAD b/tests/resources/icase/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/icase/.gitted/logs/HEAD rename to tests/resources/icase/.gitted/logs/HEAD diff --git a/tests-clar/resources/icase/.gitted/logs/refs/heads/master b/tests/resources/icase/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/icase/.gitted/logs/refs/heads/master rename to tests/resources/icase/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce b/tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce similarity index 100% rename from tests-clar/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce rename to tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce diff --git a/tests-clar/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93 b/tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93 similarity index 100% rename from tests-clar/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93 rename to tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93 diff --git a/tests-clar/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49 b/tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49 similarity index 100% rename from tests-clar/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49 rename to tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49 diff --git a/tests-clar/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6 b/tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6 similarity index 100% rename from tests-clar/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6 rename to tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6 diff --git a/tests-clar/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6 b/tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6 similarity index 100% rename from tests-clar/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6 rename to tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6 diff --git a/tests-clar/resources/icase/.gitted/refs/heads/master b/tests/resources/icase/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/icase/.gitted/refs/heads/master rename to tests/resources/icase/.gitted/refs/heads/master diff --git a/tests-clar/resources/icase/B b/tests/resources/icase/B similarity index 100% rename from tests-clar/resources/icase/B rename to tests/resources/icase/B diff --git a/tests-clar/resources/icase/D b/tests/resources/icase/D similarity index 100% rename from tests-clar/resources/icase/D rename to tests/resources/icase/D diff --git a/tests-clar/resources/icase/F b/tests/resources/icase/F similarity index 100% rename from tests-clar/resources/icase/F rename to tests/resources/icase/F diff --git a/tests-clar/resources/icase/H b/tests/resources/icase/H similarity index 100% rename from tests-clar/resources/icase/H rename to tests/resources/icase/H diff --git a/tests-clar/resources/icase/J b/tests/resources/icase/J similarity index 100% rename from tests-clar/resources/icase/J rename to tests/resources/icase/J diff --git a/tests-clar/resources/icase/L/1 b/tests/resources/icase/L/1 similarity index 100% rename from tests-clar/resources/icase/L/1 rename to tests/resources/icase/L/1 diff --git a/tests-clar/resources/icase/L/B b/tests/resources/icase/L/B similarity index 100% rename from tests-clar/resources/icase/L/B rename to tests/resources/icase/L/B diff --git a/tests-clar/resources/icase/L/D b/tests/resources/icase/L/D similarity index 100% rename from tests-clar/resources/icase/L/D rename to tests/resources/icase/L/D diff --git a/tests-clar/resources/icase/L/a b/tests/resources/icase/L/a similarity index 100% rename from tests-clar/resources/icase/L/a rename to tests/resources/icase/L/a diff --git a/tests-clar/resources/icase/L/c b/tests/resources/icase/L/c similarity index 100% rename from tests-clar/resources/icase/L/c rename to tests/resources/icase/L/c diff --git a/tests-clar/resources/icase/a b/tests/resources/icase/a similarity index 100% rename from tests-clar/resources/icase/a rename to tests/resources/icase/a diff --git a/tests-clar/resources/icase/c b/tests/resources/icase/c similarity index 100% rename from tests-clar/resources/icase/c rename to tests/resources/icase/c diff --git a/tests-clar/resources/icase/e b/tests/resources/icase/e similarity index 100% rename from tests-clar/resources/icase/e rename to tests/resources/icase/e diff --git a/tests-clar/resources/icase/g b/tests/resources/icase/g similarity index 100% rename from tests-clar/resources/icase/g rename to tests/resources/icase/g diff --git a/tests-clar/resources/icase/i b/tests/resources/icase/i similarity index 100% rename from tests-clar/resources/icase/i rename to tests/resources/icase/i diff --git a/tests-clar/resources/icase/k/1 b/tests/resources/icase/k/1 similarity index 100% rename from tests-clar/resources/icase/k/1 rename to tests/resources/icase/k/1 diff --git a/tests-clar/resources/icase/k/B b/tests/resources/icase/k/B similarity index 100% rename from tests-clar/resources/icase/k/B rename to tests/resources/icase/k/B diff --git a/tests-clar/resources/icase/k/D b/tests/resources/icase/k/D similarity index 100% rename from tests-clar/resources/icase/k/D rename to tests/resources/icase/k/D diff --git a/tests-clar/resources/icase/k/a b/tests/resources/icase/k/a similarity index 100% rename from tests-clar/resources/icase/k/a rename to tests/resources/icase/k/a diff --git a/tests-clar/resources/icase/k/c b/tests/resources/icase/k/c similarity index 100% rename from tests-clar/resources/icase/k/c rename to tests/resources/icase/k/c diff --git a/tests-clar/resources/issue_592/.gitted/HEAD b/tests/resources/issue_1397/.gitted/HEAD similarity index 100% rename from tests-clar/resources/issue_592/.gitted/HEAD rename to tests/resources/issue_1397/.gitted/HEAD diff --git a/tests-clar/resources/issue_1397/.gitted/config b/tests/resources/issue_1397/.gitted/config similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/config rename to tests/resources/issue_1397/.gitted/config diff --git a/tests-clar/resources/issue_1397/.gitted/index b/tests/resources/issue_1397/.gitted/index similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/index rename to tests/resources/issue_1397/.gitted/index diff --git a/tests-clar/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 b/tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 rename to tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 diff --git a/tests-clar/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 b/tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 rename to tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 diff --git a/tests-clar/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 b/tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 rename to tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 diff --git a/tests-clar/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac b/tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac rename to tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac diff --git a/tests-clar/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a b/tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a rename to tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a diff --git a/tests-clar/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 b/tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 rename to tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 diff --git a/tests-clar/resources/issue_1397/.gitted/refs/heads/master b/tests/resources/issue_1397/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/issue_1397/.gitted/refs/heads/master rename to tests/resources/issue_1397/.gitted/refs/heads/master diff --git a/tests-clar/resources/issue_1397/crlf_file.txt b/tests/resources/issue_1397/crlf_file.txt similarity index 100% rename from tests-clar/resources/issue_1397/crlf_file.txt rename to tests/resources/issue_1397/crlf_file.txt diff --git a/tests-clar/resources/issue_1397/some_other_crlf_file.txt b/tests/resources/issue_1397/some_other_crlf_file.txt similarity index 100% rename from tests-clar/resources/issue_1397/some_other_crlf_file.txt rename to tests/resources/issue_1397/some_other_crlf_file.txt diff --git a/tests-clar/resources/issue_592/.gitted/COMMIT_EDITMSG b/tests/resources/issue_592/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/issue_592/.gitted/COMMIT_EDITMSG rename to tests/resources/issue_592/.gitted/COMMIT_EDITMSG diff --git a/tests-clar/resources/issue_592b/.gitted/HEAD b/tests/resources/issue_592/.gitted/HEAD similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/HEAD rename to tests/resources/issue_592/.gitted/HEAD diff --git a/tests-clar/resources/issue_592/.gitted/config b/tests/resources/issue_592/.gitted/config similarity index 100% rename from tests-clar/resources/issue_592/.gitted/config rename to tests/resources/issue_592/.gitted/config diff --git a/tests-clar/resources/issue_592/.gitted/index b/tests/resources/issue_592/.gitted/index similarity index 100% rename from tests-clar/resources/issue_592/.gitted/index rename to tests/resources/issue_592/.gitted/index diff --git a/tests-clar/resources/issue_592/.gitted/info/exclude b/tests/resources/issue_592/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/issue_592/.gitted/info/exclude rename to tests/resources/issue_592/.gitted/info/exclude diff --git a/tests-clar/resources/issue_592/.gitted/logs/HEAD b/tests/resources/issue_592/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/issue_592/.gitted/logs/HEAD rename to tests/resources/issue_592/.gitted/logs/HEAD diff --git a/tests-clar/resources/issue_592/.gitted/logs/refs/heads/master b/tests/resources/issue_592/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/issue_592/.gitted/logs/refs/heads/master rename to tests/resources/issue_592/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e b/tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e rename to tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e diff --git a/tests-clar/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 b/tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 rename to tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 diff --git a/tests-clar/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 b/tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 rename to tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 diff --git a/tests-clar/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 b/tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 rename to tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 diff --git a/tests-clar/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 b/tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 rename to tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 diff --git a/tests-clar/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 b/tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 rename to tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 diff --git a/tests-clar/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 b/tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 similarity index 100% rename from tests-clar/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 rename to tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 diff --git a/tests-clar/resources/issue_592/.gitted/refs/heads/master b/tests/resources/issue_592/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/issue_592/.gitted/refs/heads/master rename to tests/resources/issue_592/.gitted/refs/heads/master diff --git a/tests-clar/resources/issue_592/a.txt b/tests/resources/issue_592/a.txt similarity index 100% rename from tests-clar/resources/issue_592/a.txt rename to tests/resources/issue_592/a.txt diff --git a/tests-clar/resources/issue_592/c/a.txt b/tests/resources/issue_592/c/a.txt similarity index 100% rename from tests-clar/resources/issue_592/c/a.txt rename to tests/resources/issue_592/c/a.txt diff --git a/tests-clar/resources/issue_592/l.txt b/tests/resources/issue_592/l.txt similarity index 100% rename from tests-clar/resources/issue_592/l.txt rename to tests/resources/issue_592/l.txt diff --git a/tests-clar/resources/issue_592/t/a.txt b/tests/resources/issue_592/t/a.txt similarity index 100% rename from tests-clar/resources/issue_592/t/a.txt rename to tests/resources/issue_592/t/a.txt diff --git a/tests-clar/resources/issue_592/t/b.txt b/tests/resources/issue_592/t/b.txt similarity index 100% rename from tests-clar/resources/issue_592/t/b.txt rename to tests/resources/issue_592/t/b.txt diff --git a/tests-clar/resources/merge-resolve/.gitted/HEAD b/tests/resources/issue_592b/.gitted/HEAD similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/HEAD rename to tests/resources/issue_592b/.gitted/HEAD diff --git a/tests-clar/resources/issue_592b/.gitted/config b/tests/resources/issue_592b/.gitted/config similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/config rename to tests/resources/issue_592b/.gitted/config diff --git a/tests-clar/resources/merge-resolve/.gitted/description b/tests/resources/issue_592b/.gitted/description similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/description rename to tests/resources/issue_592b/.gitted/description diff --git a/tests-clar/resources/issue_592b/.gitted/index b/tests/resources/issue_592b/.gitted/index similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/index rename to tests/resources/issue_592b/.gitted/index diff --git a/tests-clar/resources/issue_592b/.gitted/info/exclude b/tests/resources/issue_592b/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/info/exclude rename to tests/resources/issue_592b/.gitted/info/exclude diff --git a/tests-clar/resources/issue_592b/.gitted/logs/HEAD b/tests/resources/issue_592b/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/logs/HEAD rename to tests/resources/issue_592b/.gitted/logs/HEAD diff --git a/tests-clar/resources/issue_592b/.gitted/logs/refs/heads/master b/tests/resources/issue_592b/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/logs/refs/heads/master rename to tests/resources/issue_592b/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 b/tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 rename to tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 diff --git a/tests-clar/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f b/tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f rename to tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f diff --git a/tests-clar/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 b/tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 rename to tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 diff --git a/tests-clar/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 b/tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 rename to tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 diff --git a/tests-clar/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c b/tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c rename to tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c diff --git a/tests-clar/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc b/tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc rename to tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc diff --git a/tests-clar/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 b/tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 rename to tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 diff --git a/tests-clar/resources/issue_592b/.gitted/refs/heads/master b/tests/resources/issue_592b/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/issue_592b/.gitted/refs/heads/master rename to tests/resources/issue_592b/.gitted/refs/heads/master diff --git a/tests-clar/resources/issue_592b/gitignore b/tests/resources/issue_592b/gitignore similarity index 100% rename from tests-clar/resources/issue_592b/gitignore rename to tests/resources/issue_592b/gitignore diff --git a/tests-clar/resources/issue_592b/ignored/contained/ignored3.txt b/tests/resources/issue_592b/ignored/contained/ignored3.txt similarity index 100% rename from tests-clar/resources/issue_592b/ignored/contained/ignored3.txt rename to tests/resources/issue_592b/ignored/contained/ignored3.txt diff --git a/tests-clar/resources/issue_592b/ignored/contained/tracked3.txt b/tests/resources/issue_592b/ignored/contained/tracked3.txt similarity index 100% rename from tests-clar/resources/issue_592b/ignored/contained/tracked3.txt rename to tests/resources/issue_592b/ignored/contained/tracked3.txt diff --git a/tests-clar/resources/issue_592b/ignored/ignored2.txt b/tests/resources/issue_592b/ignored/ignored2.txt similarity index 100% rename from tests-clar/resources/issue_592b/ignored/ignored2.txt rename to tests/resources/issue_592b/ignored/ignored2.txt diff --git a/tests-clar/resources/issue_592b/ignored/tracked2.txt b/tests/resources/issue_592b/ignored/tracked2.txt similarity index 100% rename from tests-clar/resources/issue_592b/ignored/tracked2.txt rename to tests/resources/issue_592b/ignored/tracked2.txt diff --git a/tests-clar/resources/issue_592b/ignored1.txt b/tests/resources/issue_592b/ignored1.txt similarity index 100% rename from tests-clar/resources/issue_592b/ignored1.txt rename to tests/resources/issue_592b/ignored1.txt diff --git a/tests-clar/resources/issue_592b/tracked1.txt b/tests/resources/issue_592b/tracked1.txt similarity index 100% rename from tests-clar/resources/issue_592b/tracked1.txt rename to tests/resources/issue_592b/tracked1.txt diff --git a/tests-clar/resources/merge-resolve/.gitted/COMMIT_EDITMSG b/tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/COMMIT_EDITMSG rename to tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG diff --git a/tests-clar/resources/mergedrepo/.gitted/HEAD b/tests/resources/merge-resolve/.gitted/HEAD similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/HEAD rename to tests/resources/merge-resolve/.gitted/HEAD diff --git a/tests-clar/resources/merge-resolve/.gitted/ORIG_HEAD b/tests/resources/merge-resolve/.gitted/ORIG_HEAD similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/ORIG_HEAD rename to tests/resources/merge-resolve/.gitted/ORIG_HEAD diff --git a/tests-clar/resources/merge-resolve/.gitted/config b/tests/resources/merge-resolve/.gitted/config similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/config rename to tests/resources/merge-resolve/.gitted/config diff --git a/tests-clar/resources/mergedrepo/.gitted/description b/tests/resources/merge-resolve/.gitted/description similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/description rename to tests/resources/merge-resolve/.gitted/description diff --git a/tests-clar/resources/merge-resolve/.gitted/index b/tests/resources/merge-resolve/.gitted/index similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/index rename to tests/resources/merge-resolve/.gitted/index diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/HEAD b/tests/resources/merge-resolve/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/HEAD rename to tests/resources/merge-resolve/.gitted/logs/HEAD diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_side1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_side1 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side1 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_side2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/df_side2 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side2 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/master b/tests/resources/merge-resolve/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/master rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo1 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo1 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo2 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo2 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo3 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo3 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo3 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo4 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo4 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo4 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo5 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo5 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo5 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo6 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/octo6 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/octo6 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/renames1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/renames1 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/renames1 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/renames2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/renames2 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/renames2 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9 rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9 diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/unrelated b/tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/logs/refs/heads/unrelated rename to tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b b/tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b rename to tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8 b/tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8 rename to tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08 b/tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08 rename to tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0 b/tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0 rename to tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858 b/tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858 rename to tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001 b/tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001 rename to tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c b/tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c rename to tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0 b/tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0 rename to tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f b/tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f rename to tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03 b/tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03 rename to tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47 b/tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47 rename to tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe b/tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe rename to tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c b/tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c rename to tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f b/tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f rename to tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e b/tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e rename to tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 b/tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 rename to tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc b/tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc rename to tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 b/tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 rename to tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 b/tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 rename to tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 b/tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 rename to tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 b/tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 rename to tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 b/tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 rename to tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d b/tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d rename to tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 b/tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 rename to tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151 b/tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151 rename to tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 b/tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 rename to tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa b/tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa rename to tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e b/tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e rename to tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734 b/tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734 rename to tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c b/tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c rename to tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 b/tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 rename to tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982 b/tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982 rename to tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 b/tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 rename to tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 b/tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 rename to tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 b/tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 rename to tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 diff --git a/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e b/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e new file mode 100644 index 000000000..6039df00e Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e differ diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b b/tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b rename to tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 b/tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 rename to tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 b/tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 rename to tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 b/tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 rename to tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331 b/tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331 rename to tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c b/tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c rename to tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 b/tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 rename to tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b b/tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b rename to tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28 b/tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28 rename to tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d b/tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d rename to tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74 b/tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74 rename to tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 b/tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 rename to tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 b/tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 rename to tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 b/tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 rename to tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 b/tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 rename to tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add b/tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add rename to tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 b/tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 rename to tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 rename to tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f b/tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f rename to tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67 b/tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67 rename to tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c b/tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c rename to tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31 b/tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31 rename to tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 b/tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 rename to tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d b/tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d rename to tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 b/tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 rename to tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b b/tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b rename to tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475 b/tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475 rename to tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 b/tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 rename to tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 b/tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 rename to tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 b/tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 rename to tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 b/tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 rename to tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 b/tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 rename to tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29 b/tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29 rename to tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd b/tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd rename to tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 b/tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 rename to tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 b/tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 rename to tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41 b/tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41 rename to tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced b/tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced rename to tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 b/tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 rename to tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f b/tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f rename to tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa b/tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa rename to tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b b/tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b rename to tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c b/tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c rename to tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a b/tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a rename to tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 b/tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 rename to tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 b/tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 rename to tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a b/tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a rename to tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 b/tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 rename to tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 b/tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 rename to tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 b/tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 rename to tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 b/tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 rename to tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 b/tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 rename to tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 b/tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 rename to tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 b/tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 rename to tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 b/tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 rename to tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 rename to tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 b/tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 rename to tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 b/tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 rename to tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed b/tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed rename to tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 b/tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 rename to tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 b/tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 rename to tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 b/tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 rename to tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 b/tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 rename to tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9 b/tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9 rename to tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 b/tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 rename to tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 b/tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 rename to tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351 b/tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351 rename to tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 b/tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 rename to tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a b/tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a rename to tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae b/tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae rename to tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 b/tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 rename to tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd b/tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd rename to tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f b/tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f rename to tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 b/tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 rename to tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c b/tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c rename to tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb b/tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb rename to tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 b/tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 rename to tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a b/tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a rename to tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad b/tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad rename to tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 b/tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 rename to tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d b/tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d rename to tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 b/tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 rename to tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d b/tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d rename to tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f b/tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f rename to tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d b/tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d rename to tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 b/tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 rename to tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 b/tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 rename to tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb b/tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb rename to tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 b/tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 rename to tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b b/tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b rename to tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b b/tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b rename to tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a b/tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a rename to tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38 b/tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38 rename to tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 b/tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 rename to tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e b/tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e rename to tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435 b/tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435 rename to tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf b/tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf rename to tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c b/tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c rename to tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb b/tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb rename to tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468 b/tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468 rename to tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b b/tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b rename to tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9 b/tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9 rename to tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f b/tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f rename to tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531 b/tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531 rename to tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8 b/tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8 rename to tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8 diff --git a/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 b/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 new file mode 100644 index 000000000..4886e492e Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 differ diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e b/tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e rename to tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b b/tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b rename to tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060 b/tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060 rename to tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 b/tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 rename to tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 b/tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 rename to tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 b/tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 rename to tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520 b/tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520 rename to tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 b/tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 rename to tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d b/tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d rename to tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b b/tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b rename to tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d b/tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d rename to tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e b/tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e rename to tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e b/tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e rename to tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497 b/tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497 rename to tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe b/tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe rename to tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab b/tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab rename to tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231 b/tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231 rename to tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29 b/tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29 rename to tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf b/tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf rename to tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f b/tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f rename to tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f b/tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f rename to tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a b/tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a rename to tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a diff --git a/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 b/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 new file mode 100644 index 000000000..a90ee08ce Binary files /dev/null and b/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 differ diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 b/tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 rename to tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa b/tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa rename to tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 b/tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 rename to tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 b/tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 rename to tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c b/tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c rename to tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 b/tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 rename to tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 b/tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 rename to tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a b/tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a rename to tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793 b/tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793 rename to tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b b/tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b rename to tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149 b/tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149 rename to tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4 b/tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4 rename to tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f b/tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f rename to tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714 b/tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714 rename to tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db b/tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db rename to tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0 b/tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0 rename to tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7 b/tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7 rename to tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979 b/tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979 rename to tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102 b/tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102 rename to tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b b/tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b rename to tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4 b/tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4 rename to tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254 b/tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254 rename to tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4 b/tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4 rename to tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e b/tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e rename to tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4 b/tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4 rename to tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27 b/tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27 rename to tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48 b/tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48 rename to tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca b/tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca rename to tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956 b/tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956 rename to tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b b/tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b rename to tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543 b/tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543 rename to tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0 b/tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0 rename to tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972 b/tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972 rename to tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1 b/tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1 rename to tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 b/tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 rename to tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 b/tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 rename to tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b b/tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b rename to tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 b/tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 rename to tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 b/tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 rename to tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a b/tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a rename to tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 b/tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 rename to tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 b/tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 rename to tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 b/tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 rename to tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07 b/tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07 rename to tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 b/tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 rename to tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 b/tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 rename to tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d b/tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d rename to tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 b/tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 rename to tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 b/tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 rename to tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 b/tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 rename to tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 b/tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 rename to tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 b/tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 rename to tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 b/tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 rename to tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d b/tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d rename to tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10 b/tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10 rename to tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff b/tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff rename to tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090 b/tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090 rename to tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a b/tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a rename to tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301 b/tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301 rename to tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618 b/tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618 rename to tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd b/tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd rename to tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c b/tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c rename to tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec b/tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec rename to tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed b/tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed rename to tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d b/tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d rename to tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb b/tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb rename to tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa b/tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa rename to tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b b/tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b rename to tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 b/tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 rename to tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 b/tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 rename to tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f b/tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f rename to tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 b/tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 rename to tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f b/tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f rename to tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d b/tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d rename to tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 b/tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 rename to tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb b/tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb rename to tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e b/tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e rename to tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 b/tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 rename to tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 b/tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 rename to tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b b/tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b rename to tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a b/tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a rename to tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 rename to tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 b/tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 rename to tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f b/tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f rename to tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 b/tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 rename to tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 b/tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 rename to tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 b/tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 rename to tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 b/tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 rename to tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 b/tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 rename to tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b b/tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b rename to tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 b/tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 rename to tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 b/tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 rename to tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf b/tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf rename to tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 b/tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 rename to tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 b/tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 rename to tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 b/tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 rename to tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd b/tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd rename to tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7 b/tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7 rename to tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 b/tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 rename to tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 b/tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 rename to tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 b/tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 rename to tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 b/tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 rename to tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e b/tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e rename to tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 b/tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 rename to tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f b/tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f rename to tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 b/tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 rename to tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff b/tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff rename to tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246 b/tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246 rename to tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5 b/tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5 rename to tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366 b/tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366 rename to tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f b/tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f rename to tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a b/tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a rename to tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a b/tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a rename to tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be b/tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be rename to tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf b/tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf rename to tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f b/tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f rename to tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6 b/tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6 rename to tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075 b/tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075 rename to tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09 b/tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09 rename to tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e b/tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e rename to tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6 b/tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6 rename to tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753 b/tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753 rename to tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796 b/tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796 rename to tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198 b/tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198 rename to tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae b/tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae rename to tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae b/tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae rename to tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f b/tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f rename to tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2 b/tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2 rename to tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293 b/tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293 rename to tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931 b/tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931 rename to tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521 b/tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521 rename to tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf b/tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf rename to tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884 b/tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884 rename to tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 b/tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 rename to tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d b/tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d rename to tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 b/tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 rename to tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 b/tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 rename to tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 b/tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 rename to tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618 b/tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618 rename to tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 b/tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 rename to tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a b/tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a rename to tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 b/tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 rename to tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 b/tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 rename to tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 diff --git a/tests-clar/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd b/tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd rename to tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/branch b/tests/resources/merge-resolve/.gitted/refs/heads/branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/branch rename to tests/resources/merge-resolve/.gitted/refs/heads/branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/df_ancestor b/tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/df_ancestor rename to tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/df_side1 b/tests/resources/merge-resolve/.gitted/refs/heads/df_side1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/df_side1 rename to tests/resources/merge-resolve/.gitted/refs/heads/df_side1 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/df_side2 b/tests/resources/merge-resolve/.gitted/refs/heads/df_side2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/df_side2 rename to tests/resources/merge-resolve/.gitted/refs/heads/df_side2 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/ff_branch b/tests/resources/merge-resolve/.gitted/refs/heads/ff_branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/ff_branch rename to tests/resources/merge-resolve/.gitted/refs/heads/ff_branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/master b/tests/resources/merge-resolve/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/master rename to tests/resources/merge-resolve/.gitted/refs/heads/master diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo1 b/tests/resources/merge-resolve/.gitted/refs/heads/octo1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo1 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo1 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo2 b/tests/resources/merge-resolve/.gitted/refs/heads/octo2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo2 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo2 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo3 b/tests/resources/merge-resolve/.gitted/refs/heads/octo3 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo3 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo3 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo4 b/tests/resources/merge-resolve/.gitted/refs/heads/octo4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo4 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo4 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo5 b/tests/resources/merge-resolve/.gitted/refs/heads/octo5 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo5 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo5 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/octo6 b/tests/resources/merge-resolve/.gitted/refs/heads/octo6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/octo6 rename to tests/resources/merge-resolve/.gitted/refs/heads/octo6 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor rename to tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours rename to tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs rename to tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/renames1 b/tests/resources/merge-resolve/.gitted/refs/heads/renames1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/renames1 rename to tests/resources/merge-resolve/.gitted/refs/heads/renames1 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/renames2 b/tests/resources/merge-resolve/.gitted/refs/heads/renames2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/renames2 rename to tests/resources/merge-resolve/.gitted/refs/heads/renames2 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-10 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-10 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-10 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-11 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-11 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-11 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-13 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-13 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-13 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-14 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-14 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-14 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-2alt b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-2alt rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-3alt b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-3alt rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-4 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-4 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-4 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-6 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-6 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-6 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-7 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-7 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-7 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-8 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-8 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-8 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-9 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9 similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-9 rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-9 diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch rename to tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch diff --git a/tests-clar/resources/merge-resolve/.gitted/refs/heads/unrelated b/tests/resources/merge-resolve/.gitted/refs/heads/unrelated similarity index 100% rename from tests-clar/resources/merge-resolve/.gitted/refs/heads/unrelated rename to tests/resources/merge-resolve/.gitted/refs/heads/unrelated diff --git a/tests-clar/resources/merge-resolve/added-in-master.txt b/tests/resources/merge-resolve/added-in-master.txt similarity index 100% rename from tests-clar/resources/merge-resolve/added-in-master.txt rename to tests/resources/merge-resolve/added-in-master.txt diff --git a/tests-clar/resources/merge-resolve/automergeable.txt b/tests/resources/merge-resolve/automergeable.txt similarity index 100% rename from tests-clar/resources/merge-resolve/automergeable.txt rename to tests/resources/merge-resolve/automergeable.txt diff --git a/tests-clar/resources/merge-resolve/changed-in-branch.txt b/tests/resources/merge-resolve/changed-in-branch.txt similarity index 100% rename from tests-clar/resources/merge-resolve/changed-in-branch.txt rename to tests/resources/merge-resolve/changed-in-branch.txt diff --git a/tests-clar/resources/merge-resolve/changed-in-master.txt b/tests/resources/merge-resolve/changed-in-master.txt similarity index 100% rename from tests-clar/resources/merge-resolve/changed-in-master.txt rename to tests/resources/merge-resolve/changed-in-master.txt diff --git a/tests-clar/resources/merge-resolve/conflicting.txt b/tests/resources/merge-resolve/conflicting.txt similarity index 100% rename from tests-clar/resources/merge-resolve/conflicting.txt rename to tests/resources/merge-resolve/conflicting.txt diff --git a/tests-clar/resources/merge-resolve/removed-in-branch.txt b/tests/resources/merge-resolve/removed-in-branch.txt similarity index 100% rename from tests-clar/resources/merge-resolve/removed-in-branch.txt rename to tests/resources/merge-resolve/removed-in-branch.txt diff --git a/tests-clar/resources/merge-resolve/unchanged.txt b/tests/resources/merge-resolve/unchanged.txt similarity index 100% rename from tests-clar/resources/merge-resolve/unchanged.txt rename to tests/resources/merge-resolve/unchanged.txt diff --git a/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG b/tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG rename to tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG diff --git a/tests-clar/resources/peeled.git/HEAD b/tests/resources/mergedrepo/.gitted/HEAD similarity index 100% rename from tests-clar/resources/peeled.git/HEAD rename to tests/resources/mergedrepo/.gitted/HEAD diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD b/tests/resources/mergedrepo/.gitted/MERGE_HEAD similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD rename to tests/resources/mergedrepo/.gitted/MERGE_HEAD diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE b/tests/resources/mergedrepo/.gitted/MERGE_MODE similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/MERGE_MODE rename to tests/resources/mergedrepo/.gitted/MERGE_MODE diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG b/tests/resources/mergedrepo/.gitted/MERGE_MSG similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/MERGE_MSG rename to tests/resources/mergedrepo/.gitted/MERGE_MSG diff --git a/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD b/tests/resources/mergedrepo/.gitted/ORIG_HEAD similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD rename to tests/resources/mergedrepo/.gitted/ORIG_HEAD diff --git a/tests-clar/resources/mergedrepo/.gitted/config b/tests/resources/mergedrepo/.gitted/config similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/config rename to tests/resources/mergedrepo/.gitted/config diff --git a/tests-clar/resources/push_src/.gitted/description b/tests/resources/mergedrepo/.gitted/description similarity index 100% rename from tests-clar/resources/push_src/.gitted/description rename to tests/resources/mergedrepo/.gitted/description diff --git a/tests-clar/resources/mergedrepo/.gitted/index b/tests/resources/mergedrepo/.gitted/index similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/index rename to tests/resources/mergedrepo/.gitted/index diff --git a/tests-clar/resources/mergedrepo/.gitted/info/exclude b/tests/resources/mergedrepo/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/info/exclude rename to tests/resources/mergedrepo/.gitted/info/exclude diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/HEAD b/tests/resources/mergedrepo/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/logs/HEAD rename to tests/resources/mergedrepo/.gitted/logs/HEAD diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch b/tests/resources/mergedrepo/.gitted/logs/refs/heads/branch similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch rename to tests/resources/mergedrepo/.gitted/logs/refs/heads/branch diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master b/tests/resources/mergedrepo/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master rename to tests/resources/mergedrepo/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 b/tests/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 rename to tests/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 b/tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 rename to tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 b/tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 rename to tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e b/tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e rename to tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 b/tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 rename to tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 b/tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 rename to tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 b/tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 rename to tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c b/tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c rename to tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 b/tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 rename to tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da b/tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da rename to tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad b/tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad rename to tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 b/tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 rename to tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 b/tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 rename to tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 b/tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 rename to tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 b/tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 rename to tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c b/tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c rename to tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda b/tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda rename to tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 b/tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 rename to tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a b/tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a rename to tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 b/tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 rename to tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 b/tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 rename to tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 b/tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 rename to tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 b/tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 rename to tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 b/tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 rename to tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c b/tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c rename to tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d b/tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d rename to tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 b/tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 rename to tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff b/tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff rename to tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 b/tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 rename to tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 b/tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 rename to tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch b/tests/resources/mergedrepo/.gitted/refs/heads/branch similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/refs/heads/branch rename to tests/resources/mergedrepo/.gitted/refs/heads/branch diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/master b/tests/resources/mergedrepo/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/mergedrepo/.gitted/refs/heads/master rename to tests/resources/mergedrepo/.gitted/refs/heads/master diff --git a/tests-clar/resources/mergedrepo/conflicts-one.txt b/tests/resources/mergedrepo/conflicts-one.txt similarity index 100% rename from tests-clar/resources/mergedrepo/conflicts-one.txt rename to tests/resources/mergedrepo/conflicts-one.txt diff --git a/tests-clar/resources/mergedrepo/conflicts-two.txt b/tests/resources/mergedrepo/conflicts-two.txt similarity index 100% rename from tests-clar/resources/mergedrepo/conflicts-two.txt rename to tests/resources/mergedrepo/conflicts-two.txt diff --git a/tests-clar/resources/mergedrepo/one.txt b/tests/resources/mergedrepo/one.txt similarity index 100% rename from tests-clar/resources/mergedrepo/one.txt rename to tests/resources/mergedrepo/one.txt diff --git a/tests-clar/resources/mergedrepo/two.txt b/tests/resources/mergedrepo/two.txt similarity index 100% rename from tests-clar/resources/mergedrepo/two.txt rename to tests/resources/mergedrepo/two.txt diff --git a/tests-clar/resources/partial-testrepo/.gitted/HEAD b/tests/resources/partial-testrepo/.gitted/HEAD similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/HEAD rename to tests/resources/partial-testrepo/.gitted/HEAD diff --git a/tests-clar/resources/partial-testrepo/.gitted/config b/tests/resources/partial-testrepo/.gitted/config similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/config rename to tests/resources/partial-testrepo/.gitted/config diff --git a/tests-clar/resources/partial-testrepo/.gitted/index b/tests/resources/partial-testrepo/.gitted/index similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/index rename to tests/resources/partial-testrepo/.gitted/index diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e rename to tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 rename to tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 rename to tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc rename to tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 rename to tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 rename to tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 rename to tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep b/tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep rename to tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep diff --git a/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir b/tests/resources/partial-testrepo/.gitted/refs/heads/dir similarity index 100% rename from tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir rename to tests/resources/partial-testrepo/.gitted/refs/heads/dir diff --git a/tests-clar/resources/push_src/.gitted/HEAD b/tests/resources/peeled.git/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/HEAD rename to tests/resources/peeled.git/HEAD diff --git a/tests-clar/resources/peeled.git/config b/tests/resources/peeled.git/config similarity index 100% rename from tests-clar/resources/peeled.git/config rename to tests/resources/peeled.git/config diff --git a/tests-clar/resources/peeled.git/objects/info/packs b/tests/resources/peeled.git/objects/info/packs similarity index 100% rename from tests-clar/resources/peeled.git/objects/info/packs rename to tests/resources/peeled.git/objects/info/packs diff --git a/tests-clar/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx similarity index 100% rename from tests-clar/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx rename to tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx diff --git a/tests-clar/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack similarity index 100% rename from tests-clar/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack rename to tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack diff --git a/tests-clar/resources/peeled.git/packed-refs b/tests/resources/peeled.git/packed-refs similarity index 100% rename from tests-clar/resources/peeled.git/packed-refs rename to tests/resources/peeled.git/packed-refs diff --git a/tests-clar/resources/peeled.git/refs/heads/master b/tests/resources/peeled.git/refs/heads/master similarity index 100% rename from tests-clar/resources/peeled.git/refs/heads/master rename to tests/resources/peeled.git/refs/heads/master diff --git a/tests-clar/resources/push.sh b/tests/resources/push.sh similarity index 100% rename from tests-clar/resources/push.sh rename to tests/resources/push.sh diff --git a/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG b/tests/resources/push_src/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG rename to tests/resources/push_src/.gitted/COMMIT_EDITMSG diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD b/tests/resources/push_src/.gitted/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/HEAD rename to tests/resources/push_src/.gitted/HEAD diff --git a/tests-clar/resources/push_src/.gitted/ORIG_HEAD b/tests/resources/push_src/.gitted/ORIG_HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/ORIG_HEAD rename to tests/resources/push_src/.gitted/ORIG_HEAD diff --git a/tests-clar/resources/push_src/.gitted/config b/tests/resources/push_src/.gitted/config similarity index 100% rename from tests-clar/resources/push_src/.gitted/config rename to tests/resources/push_src/.gitted/config diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/description b/tests/resources/push_src/.gitted/description similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/description rename to tests/resources/push_src/.gitted/description diff --git a/tests-clar/resources/push_src/.gitted/index b/tests/resources/push_src/.gitted/index similarity index 100% rename from tests-clar/resources/push_src/.gitted/index rename to tests/resources/push_src/.gitted/index diff --git a/tests-clar/resources/push_src/.gitted/info/exclude b/tests/resources/push_src/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/push_src/.gitted/info/exclude rename to tests/resources/push_src/.gitted/info/exclude diff --git a/tests-clar/resources/push_src/.gitted/logs/HEAD b/tests/resources/push_src/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/HEAD rename to tests/resources/push_src/.gitted/logs/HEAD diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 b/tests/resources/push_src/.gitted/logs/refs/heads/b1 similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 rename to tests/resources/push_src/.gitted/logs/refs/heads/b1 diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 b/tests/resources/push_src/.gitted/logs/refs/heads/b2 similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 rename to tests/resources/push_src/.gitted/logs/refs/heads/b2 diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 b/tests/resources/push_src/.gitted/logs/refs/heads/b3 similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 rename to tests/resources/push_src/.gitted/logs/refs/heads/b3 diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 b/tests/resources/push_src/.gitted/logs/refs/heads/b4 similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 rename to tests/resources/push_src/.gitted/logs/refs/heads/b4 diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 b/tests/resources/push_src/.gitted/logs/refs/heads/b5 similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 rename to tests/resources/push_src/.gitted/logs/refs/heads/b5 diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/master b/tests/resources/push_src/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/push_src/.gitted/logs/refs/heads/master rename to tests/resources/push_src/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/renames/.gitted/HEAD b/tests/resources/push_src/.gitted/modules/submodule/HEAD similarity index 100% rename from tests-clar/resources/renames/.gitted/HEAD rename to tests/resources/push_src/.gitted/modules/submodule/HEAD diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/config b/tests/resources/push_src/.gitted/modules/submodule/config similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/config rename to tests/resources/push_src/.gitted/modules/submodule/config diff --git a/tests-clar/resources/renames/.gitted/description b/tests/resources/push_src/.gitted/modules/submodule/description similarity index 100% rename from tests-clar/resources/renames/.gitted/description rename to tests/resources/push_src/.gitted/modules/submodule/description diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/index b/tests/resources/push_src/.gitted/modules/submodule/index similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/index rename to tests/resources/push_src/.gitted/modules/submodule/index diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude b/tests/resources/push_src/.gitted/modules/submodule/info/exclude similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude rename to tests/resources/push_src/.gitted/modules/submodule/info/exclude diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD b/tests/resources/push_src/.gitted/modules/submodule/logs/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD rename to tests/resources/push_src/.gitted/modules/submodule/logs/HEAD diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master rename to tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD rename to tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 rename to tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd rename to tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 rename to tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b rename to tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 rename to tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d rename to tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 rename to tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 rename to tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc rename to tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea rename to tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 rename to tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af rename to tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 rename to tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe rename to tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 rename to tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 rename to tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 rename to tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 rename to tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 rename to tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 rename to tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 rename to tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 rename to tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 b/tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 rename to tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f rename to tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 b/tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 rename to tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 rename to tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 rename to tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 rename to tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack rename to tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs b/tests/resources/push_src/.gitted/modules/submodule/packed-refs similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs rename to tests/resources/push_src/.gitted/modules/submodule/packed-refs diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master b/tests/resources/push_src/.gitted/modules/submodule/refs/heads/master similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master rename to tests/resources/push_src/.gitted/modules/submodule/refs/heads/master diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD b/tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD rename to tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca b/tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca rename to tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca diff --git a/tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d b/tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d rename to tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d diff --git a/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 b/tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 rename to tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 diff --git a/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 b/tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 rename to tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 diff --git a/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 b/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 new file mode 100644 index 000000000..0bc57f266 Binary files /dev/null and b/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 b/tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 rename to tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 diff --git a/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 b/tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 rename to tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 diff --git a/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d b/tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d rename to tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d diff --git a/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 b/tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 rename to tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 diff --git a/tests-clar/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac b/tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac rename to tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac diff --git a/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce b/tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce rename to tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce diff --git a/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 b/tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 rename to tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 diff --git a/tests-clar/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498 b/tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498 rename to tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498 diff --git a/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd b/tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd rename to tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd diff --git a/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 b/tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 rename to tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 diff --git a/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c b/tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c rename to tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c diff --git a/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 b/tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 rename to tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 diff --git a/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 b/tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 rename to tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 diff --git a/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 b/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 new file mode 100644 index 000000000..b7b81d5e3 Binary files /dev/null and b/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 differ diff --git a/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 b/tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 rename to tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 diff --git a/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a b/tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a rename to tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a diff --git a/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 b/tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 rename to tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 diff --git a/tests-clar/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e b/tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e rename to tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e diff --git a/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 b/tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 rename to tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 diff --git a/tests-clar/resources/push_src/.gitted/objects/pack/dummy b/tests/resources/push_src/.gitted/objects/pack/dummy similarity index 100% rename from tests-clar/resources/push_src/.gitted/objects/pack/dummy rename to tests/resources/push_src/.gitted/objects/pack/dummy diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b1 b/tests/resources/push_src/.gitted/refs/heads/b1 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b1 rename to tests/resources/push_src/.gitted/refs/heads/b1 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b2 b/tests/resources/push_src/.gitted/refs/heads/b2 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b2 rename to tests/resources/push_src/.gitted/refs/heads/b2 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b3 b/tests/resources/push_src/.gitted/refs/heads/b3 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b3 rename to tests/resources/push_src/.gitted/refs/heads/b3 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b4 b/tests/resources/push_src/.gitted/refs/heads/b4 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b4 rename to tests/resources/push_src/.gitted/refs/heads/b4 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b5 b/tests/resources/push_src/.gitted/refs/heads/b5 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b5 rename to tests/resources/push_src/.gitted/refs/heads/b5 diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b6 b/tests/resources/push_src/.gitted/refs/heads/b6 similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/heads/b6 rename to tests/resources/push_src/.gitted/refs/heads/b6 diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-blob b/tests/resources/push_src/.gitted/refs/tags/tag-blob similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/tags/tag-blob rename to tests/resources/push_src/.gitted/refs/tags/tag-blob diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-commit b/tests/resources/push_src/.gitted/refs/tags/tag-commit similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/tags/tag-commit rename to tests/resources/push_src/.gitted/refs/tags/tag-commit diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-commit-two b/tests/resources/push_src/.gitted/refs/tags/tag-commit-two new file mode 100644 index 000000000..abb365080 Binary files /dev/null and b/tests/resources/push_src/.gitted/refs/tags/tag-commit-two differ diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-lightweight b/tests/resources/push_src/.gitted/refs/tags/tag-lightweight similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/tags/tag-lightweight rename to tests/resources/push_src/.gitted/refs/tags/tag-lightweight diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-tag b/tests/resources/push_src/.gitted/refs/tags/tag-tag new file mode 100644 index 000000000..d6cd74804 Binary files /dev/null and b/tests/resources/push_src/.gitted/refs/tags/tag-tag differ diff --git a/tests-clar/resources/push_src/.gitted/refs/tags/tag-tree b/tests/resources/push_src/.gitted/refs/tags/tag-tree similarity index 100% rename from tests-clar/resources/push_src/.gitted/refs/tags/tag-tree rename to tests/resources/push_src/.gitted/refs/tags/tag-tree diff --git a/tests-clar/resources/push_src/a.txt b/tests/resources/push_src/a.txt similarity index 100% rename from tests-clar/resources/push_src/a.txt rename to tests/resources/push_src/a.txt diff --git a/tests-clar/resources/push_src/fold/b.txt b/tests/resources/push_src/fold/b.txt similarity index 100% rename from tests-clar/resources/push_src/fold/b.txt rename to tests/resources/push_src/fold/b.txt diff --git a/tests-clar/resources/push_src/foldb.txt b/tests/resources/push_src/foldb.txt similarity index 100% rename from tests-clar/resources/push_src/foldb.txt rename to tests/resources/push_src/foldb.txt diff --git a/tests-clar/resources/push_src/gitmodules b/tests/resources/push_src/gitmodules similarity index 100% rename from tests-clar/resources/push_src/gitmodules rename to tests/resources/push_src/gitmodules diff --git a/tests-clar/resources/push_src/submodule/.gitted b/tests/resources/push_src/submodule/.gitted similarity index 100% rename from tests-clar/resources/push_src/submodule/.gitted rename to tests/resources/push_src/submodule/.gitted diff --git a/tests-clar/resources/push_src/submodule/README b/tests/resources/push_src/submodule/README similarity index 100% rename from tests-clar/resources/push_src/submodule/README rename to tests/resources/push_src/submodule/README diff --git a/tests-clar/resources/push_src/submodule/branch_file.txt b/tests/resources/push_src/submodule/branch_file.txt similarity index 100% rename from tests-clar/resources/push_src/submodule/branch_file.txt rename to tests/resources/push_src/submodule/branch_file.txt diff --git a/tests-clar/resources/push_src/submodule/new.txt b/tests/resources/push_src/submodule/new.txt similarity index 100% rename from tests-clar/resources/push_src/submodule/new.txt rename to tests/resources/push_src/submodule/new.txt diff --git a/tests-clar/resources/shallow.git/HEAD b/tests/resources/renames/.gitted/HEAD similarity index 100% rename from tests-clar/resources/shallow.git/HEAD rename to tests/resources/renames/.gitted/HEAD diff --git a/tests-clar/resources/renames/.gitted/config b/tests/resources/renames/.gitted/config similarity index 100% rename from tests-clar/resources/renames/.gitted/config rename to tests/resources/renames/.gitted/config diff --git a/tests-clar/resources/status/.gitted/description b/tests/resources/renames/.gitted/description similarity index 100% rename from tests-clar/resources/status/.gitted/description rename to tests/resources/renames/.gitted/description diff --git a/tests-clar/resources/renames/.gitted/index b/tests/resources/renames/.gitted/index similarity index 100% rename from tests-clar/resources/renames/.gitted/index rename to tests/resources/renames/.gitted/index diff --git a/tests-clar/resources/renames/.gitted/info/exclude b/tests/resources/renames/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/renames/.gitted/info/exclude rename to tests/resources/renames/.gitted/info/exclude diff --git a/tests-clar/resources/renames/.gitted/logs/HEAD b/tests/resources/renames/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/renames/.gitted/logs/HEAD rename to tests/resources/renames/.gitted/logs/HEAD diff --git a/tests-clar/resources/renames/.gitted/logs/refs/heads/master b/tests/resources/renames/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/renames/.gitted/logs/refs/heads/master rename to tests/resources/renames/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 b/tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 rename to tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 diff --git a/tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 b/tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 rename to tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 diff --git a/tests-clar/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 b/tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 rename to tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 diff --git a/tests-clar/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084 b/tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084 rename to tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084 diff --git a/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a b/tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a rename to tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a diff --git a/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 b/tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 rename to tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 diff --git a/tests-clar/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754 b/tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754 rename to tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754 diff --git a/tests-clar/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d b/tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d rename to tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d diff --git a/tests-clar/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2 b/tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2 rename to tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2 diff --git a/tests-clar/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512 b/tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512 rename to tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512 diff --git a/tests-clar/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a b/tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a rename to tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a diff --git a/tests-clar/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951 b/tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951 rename to tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951 diff --git a/tests-clar/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953 b/tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953 rename to tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953 diff --git a/tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 b/tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 rename to tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 diff --git a/tests-clar/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109 b/tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109 rename to tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109 diff --git a/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 b/tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 rename to tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 diff --git a/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba b/tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba rename to tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba diff --git a/tests-clar/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7 b/tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7 rename to tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7 diff --git a/tests-clar/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935 b/tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935 rename to tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935 diff --git a/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 b/tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 rename to tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 diff --git a/tests-clar/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f b/tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f rename to tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f diff --git a/tests-clar/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323 b/tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323 rename to tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323 diff --git a/tests-clar/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b b/tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b rename to tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b diff --git a/tests-clar/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b b/tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b rename to tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b diff --git a/tests-clar/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5 b/tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5 similarity index 100% rename from tests-clar/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5 rename to tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5 diff --git a/tests-clar/resources/renames/.gitted/refs/heads/master b/tests/resources/renames/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/renames/.gitted/refs/heads/master rename to tests/resources/renames/.gitted/refs/heads/master diff --git a/tests-clar/resources/renames/.gitted/refs/heads/renames_similar b/tests/resources/renames/.gitted/refs/heads/renames_similar similarity index 100% rename from tests-clar/resources/renames/.gitted/refs/heads/renames_similar rename to tests/resources/renames/.gitted/refs/heads/renames_similar diff --git a/tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two b/tests/resources/renames/.gitted/refs/heads/renames_similar_two similarity index 100% rename from tests-clar/resources/renames/.gitted/refs/heads/renames_similar_two rename to tests/resources/renames/.gitted/refs/heads/renames_similar_two diff --git a/tests-clar/resources/renames/ikeepsix.txt b/tests/resources/renames/ikeepsix.txt similarity index 100% rename from tests-clar/resources/renames/ikeepsix.txt rename to tests/resources/renames/ikeepsix.txt diff --git a/tests-clar/resources/renames/sixserving.txt b/tests/resources/renames/sixserving.txt similarity index 100% rename from tests-clar/resources/renames/sixserving.txt rename to tests/resources/renames/sixserving.txt diff --git a/tests-clar/resources/renames/songof7cities.txt b/tests/resources/renames/songof7cities.txt similarity index 100% rename from tests-clar/resources/renames/songof7cities.txt rename to tests/resources/renames/songof7cities.txt diff --git a/tests-clar/resources/renames/untimely.txt b/tests/resources/renames/untimely.txt similarity index 100% rename from tests-clar/resources/renames/untimely.txt rename to tests/resources/renames/untimely.txt diff --git a/tests-clar/resources/short_tag.git/HEAD b/tests/resources/shallow.git/HEAD similarity index 100% rename from tests-clar/resources/short_tag.git/HEAD rename to tests/resources/shallow.git/HEAD diff --git a/tests-clar/resources/shallow.git/config b/tests/resources/shallow.git/config similarity index 100% rename from tests-clar/resources/shallow.git/config rename to tests/resources/shallow.git/config diff --git a/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx similarity index 100% rename from tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx rename to tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx diff --git a/tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack similarity index 100% rename from tests-clar/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack rename to tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack diff --git a/tests-clar/resources/shallow.git/packed-refs b/tests/resources/shallow.git/packed-refs similarity index 100% rename from tests-clar/resources/shallow.git/packed-refs rename to tests/resources/shallow.git/packed-refs diff --git a/tests-clar/resources/shallow.git/refs/.gitkeep b/tests/resources/shallow.git/refs/.gitkeep similarity index 100% rename from tests-clar/resources/shallow.git/refs/.gitkeep rename to tests/resources/shallow.git/refs/.gitkeep diff --git a/tests-clar/resources/shallow.git/shallow b/tests/resources/shallow.git/shallow similarity index 100% rename from tests-clar/resources/shallow.git/shallow rename to tests/resources/shallow.git/shallow diff --git a/tests-clar/resources/status/.gitted/HEAD b/tests/resources/short_tag.git/HEAD similarity index 100% rename from tests-clar/resources/status/.gitted/HEAD rename to tests/resources/short_tag.git/HEAD diff --git a/tests-clar/resources/duplicate.git/config b/tests/resources/short_tag.git/config similarity index 84% rename from tests-clar/resources/duplicate.git/config rename to tests/resources/short_tag.git/config index 515f48362..a4ef456cb 100644 Binary files a/tests-clar/resources/duplicate.git/config and b/tests/resources/short_tag.git/config differ diff --git a/tests-clar/resources/short_tag.git/index b/tests/resources/short_tag.git/index similarity index 100% rename from tests-clar/resources/short_tag.git/index rename to tests/resources/short_tag.git/index diff --git a/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c b/tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c similarity index 100% rename from tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c rename to tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c diff --git a/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c b/tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c similarity index 100% rename from tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c rename to tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c diff --git a/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 b/tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 similarity index 100% rename from tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 rename to tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 diff --git a/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/short_tag.git/packed-refs b/tests/resources/short_tag.git/packed-refs similarity index 100% rename from tests-clar/resources/short_tag.git/packed-refs rename to tests/resources/short_tag.git/packed-refs diff --git a/tests-clar/resources/short_tag.git/refs/heads/master b/tests/resources/short_tag.git/refs/heads/master similarity index 100% rename from tests-clar/resources/short_tag.git/refs/heads/master rename to tests/resources/short_tag.git/refs/heads/master diff --git a/tests-clar/resources/status/.gitted/COMMIT_EDITMSG b/tests/resources/status/.gitted/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/status/.gitted/COMMIT_EDITMSG rename to tests/resources/status/.gitted/COMMIT_EDITMSG diff --git a/tests-clar/resources/submod2/.gitted/HEAD b/tests/resources/status/.gitted/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/HEAD rename to tests/resources/status/.gitted/HEAD diff --git a/tests-clar/resources/status/.gitted/ORIG_HEAD b/tests/resources/status/.gitted/ORIG_HEAD similarity index 100% rename from tests-clar/resources/status/.gitted/ORIG_HEAD rename to tests/resources/status/.gitted/ORIG_HEAD diff --git a/tests-clar/resources/status/.gitted/config b/tests/resources/status/.gitted/config similarity index 100% rename from tests-clar/resources/status/.gitted/config rename to tests/resources/status/.gitted/config diff --git a/tests-clar/resources/submod2/.gitted/description b/tests/resources/status/.gitted/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/description rename to tests/resources/status/.gitted/description diff --git a/tests-clar/resources/status/.gitted/index b/tests/resources/status/.gitted/index similarity index 100% rename from tests-clar/resources/status/.gitted/index rename to tests/resources/status/.gitted/index diff --git a/tests-clar/resources/status/.gitted/info/exclude b/tests/resources/status/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/status/.gitted/info/exclude rename to tests/resources/status/.gitted/info/exclude diff --git a/tests-clar/resources/status/.gitted/logs/HEAD b/tests/resources/status/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/status/.gitted/logs/HEAD rename to tests/resources/status/.gitted/logs/HEAD diff --git a/tests-clar/resources/status/.gitted/logs/refs/heads/master b/tests/resources/status/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/status/.gitted/logs/refs/heads/master rename to tests/resources/status/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 b/tests/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 rename to tests/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 diff --git a/tests-clar/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 b/tests/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 rename to tests/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 diff --git a/tests-clar/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 b/tests/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 rename to tests/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 diff --git a/tests-clar/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 b/tests/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 rename to tests/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 diff --git a/tests-clar/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f b/tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f similarity index 100% rename from tests-clar/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f rename to tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f diff --git a/tests-clar/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c b/tests/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c similarity index 100% rename from tests-clar/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c rename to tests/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c diff --git a/tests-clar/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 b/tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 rename to tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 diff --git a/tests-clar/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a b/tests/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a similarity index 100% rename from tests-clar/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a rename to tests/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a diff --git a/tests-clar/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a b/tests/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a similarity index 100% rename from tests-clar/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a rename to tests/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a diff --git a/tests-clar/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 b/tests/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 rename to tests/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 diff --git a/tests-clar/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f b/tests/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f similarity index 100% rename from tests-clar/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f rename to tests/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f diff --git a/tests-clar/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 b/tests/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 rename to tests/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 diff --git a/tests-clar/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 b/tests/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 rename to tests/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 diff --git a/tests-clar/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 b/tests/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 rename to tests/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 diff --git a/tests-clar/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea b/tests/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea similarity index 100% rename from tests-clar/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea rename to tests/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea diff --git a/tests-clar/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 b/tests/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 rename to tests/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 diff --git a/tests-clar/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 b/tests/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 rename to tests/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 diff --git a/tests-clar/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 b/tests/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 rename to tests/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 diff --git a/tests-clar/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e b/tests/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e similarity index 100% rename from tests-clar/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e rename to tests/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e diff --git a/tests-clar/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 b/tests/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 rename to tests/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 diff --git a/tests-clar/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b b/tests/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b similarity index 100% rename from tests-clar/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b rename to tests/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b diff --git a/tests-clar/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 b/tests/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 rename to tests/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 diff --git a/tests-clar/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 b/tests/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 similarity index 100% rename from tests-clar/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 rename to tests/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 diff --git a/tests-clar/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e b/tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e similarity index 100% rename from tests-clar/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e rename to tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e diff --git a/tests-clar/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca b/tests/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca similarity index 100% rename from tests-clar/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca rename to tests/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca diff --git a/tests-clar/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd b/tests/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd similarity index 100% rename from tests-clar/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd rename to tests/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd diff --git a/tests-clar/resources/status/.gitted/refs/heads/master b/tests/resources/status/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/status/.gitted/refs/heads/master rename to tests/resources/status/.gitted/refs/heads/master diff --git a/tests-clar/resources/status/current_file b/tests/resources/status/current_file similarity index 100% rename from tests-clar/resources/status/current_file rename to tests/resources/status/current_file diff --git a/tests-clar/resources/status/ignored_file b/tests/resources/status/ignored_file similarity index 100% rename from tests-clar/resources/status/ignored_file rename to tests/resources/status/ignored_file diff --git a/tests-clar/resources/status/modified_file b/tests/resources/status/modified_file similarity index 100% rename from tests-clar/resources/status/modified_file rename to tests/resources/status/modified_file diff --git a/tests-clar/resources/status/new_file b/tests/resources/status/new_file similarity index 100% rename from tests-clar/resources/status/new_file rename to tests/resources/status/new_file diff --git a/tests-clar/resources/status/staged_changes b/tests/resources/status/staged_changes similarity index 100% rename from tests-clar/resources/status/staged_changes rename to tests/resources/status/staged_changes diff --git a/tests-clar/resources/status/staged_changes_modified_file b/tests/resources/status/staged_changes_modified_file similarity index 100% rename from tests-clar/resources/status/staged_changes_modified_file rename to tests/resources/status/staged_changes_modified_file diff --git a/tests-clar/resources/status/staged_delete_modified_file b/tests/resources/status/staged_delete_modified_file similarity index 100% rename from tests-clar/resources/status/staged_delete_modified_file rename to tests/resources/status/staged_delete_modified_file diff --git a/tests-clar/resources/status/staged_new_file b/tests/resources/status/staged_new_file similarity index 100% rename from tests-clar/resources/status/staged_new_file rename to tests/resources/status/staged_new_file diff --git a/tests-clar/resources/status/staged_new_file_modified_file b/tests/resources/status/staged_new_file_modified_file similarity index 100% rename from tests-clar/resources/status/staged_new_file_modified_file rename to tests/resources/status/staged_new_file_modified_file diff --git a/tests-clar/resources/status/subdir.txt b/tests/resources/status/subdir.txt similarity index 100% rename from tests-clar/resources/status/subdir.txt rename to tests/resources/status/subdir.txt diff --git a/tests-clar/resources/status/subdir/current_file b/tests/resources/status/subdir/current_file similarity index 100% rename from tests-clar/resources/status/subdir/current_file rename to tests/resources/status/subdir/current_file diff --git a/tests-clar/resources/status/subdir/modified_file b/tests/resources/status/subdir/modified_file similarity index 100% rename from tests-clar/resources/status/subdir/modified_file rename to tests/resources/status/subdir/modified_file diff --git a/tests-clar/resources/status/subdir/new_file b/tests/resources/status/subdir/new_file similarity index 100% rename from tests-clar/resources/status/subdir/new_file rename to tests/resources/status/subdir/new_file diff --git a/tests-clar/resources/status/这 b/tests/resources/status/这 similarity index 100% rename from tests-clar/resources/status/这 rename to tests/resources/status/这 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD b/tests/resources/submod2/.gitted/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD rename to tests/resources/submod2/.gitted/HEAD diff --git a/tests-clar/resources/submod2/.gitted/config b/tests/resources/submod2/.gitted/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/config rename to tests/resources/submod2/.gitted/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/description b/tests/resources/submod2/.gitted/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/description rename to tests/resources/submod2/.gitted/description diff --git a/tests-clar/resources/submod2/.gitted/index b/tests/resources/submod2/.gitted/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/index rename to tests/resources/submod2/.gitted/index diff --git a/tests-clar/resources/submod2/.gitted/info/exclude b/tests/resources/submod2/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/info/exclude rename to tests/resources/submod2/.gitted/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/logs/HEAD b/tests/resources/submod2/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/logs/HEAD rename to tests/resources/submod2/.gitted/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/logs/refs/heads/master b/tests/resources/submod2/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/logs/refs/heads/master rename to tests/resources/submod2/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/HEAD rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/config b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/config rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/description b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/description rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/index b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/index rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/config b/tests/resources/submod2/.gitted/modules/sm_changed_file/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/config rename to tests/resources/submod2/.gitted/modules/sm_changed_file/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/description b/tests/resources/submod2/.gitted/modules/sm_changed_file/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/description rename to tests/resources/submod2/.gitted/modules/sm_changed_file/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/index b/tests/resources/submod2/.gitted/modules/sm_changed_file/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/index rename to tests/resources/submod2/.gitted/modules/sm_changed_file/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_file/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_changed_file/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG b/tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG rename to tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/config b/tests/resources/submod2/.gitted/modules/sm_changed_head/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/config rename to tests/resources/submod2/.gitted/modules/sm_changed_head/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/description b/tests/resources/submod2/.gitted/modules/sm_changed_head/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/description rename to tests/resources/submod2/.gitted/modules/sm_changed_head/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/index b/tests/resources/submod2/.gitted/modules/sm_changed_head/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/index rename to tests/resources/submod2/.gitted/modules/sm_changed_head/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_head/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_changed_head/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/config b/tests/resources/submod2/.gitted/modules/sm_changed_index/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/config rename to tests/resources/submod2/.gitted/modules/sm_changed_index/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/description b/tests/resources/submod2/.gitted/modules/sm_changed_index/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/description rename to tests/resources/submod2/.gitted/modules/sm_changed_index/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/index b/tests/resources/submod2/.gitted/modules/sm_changed_index/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/index rename to tests/resources/submod2/.gitted/modules/sm_changed_index/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_index/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_changed_index/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/config b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/config rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/description b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/description rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/index b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/index rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/HEAD rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/config b/tests/resources/submod2/.gitted/modules/sm_missing_commits/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/config rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/config diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/description b/tests/resources/submod2/.gitted/modules/sm_missing_commits/description similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/description rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/index b/tests/resources/submod2/.gitted/modules/sm_missing_commits/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/index rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude b/tests/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs b/tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/HEAD rename to tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/config b/tests/resources/submod2/.gitted/modules/sm_unchanged/config similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/config rename to tests/resources/submod2/.gitted/modules/sm_unchanged/config diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/description b/tests/resources/submod2/.gitted/modules/sm_unchanged/description similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/description rename to tests/resources/submod2/.gitted/modules/sm_unchanged/description diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/index b/tests/resources/submod2/.gitted/modules/sm_unchanged/index similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/index rename to tests/resources/submod2/.gitted/modules/sm_unchanged/index diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/info/exclude b/tests/resources/submod2/.gitted/modules/sm_unchanged/info/exclude similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/info/exclude rename to tests/resources/submod2/.gitted/modules/sm_unchanged/info/exclude diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD rename to tests/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/packed-refs b/tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/packed-refs rename to tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master rename to tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD rename to tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e b/tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e rename to tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e diff --git a/tests-clar/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 b/tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 rename to tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 diff --git a/tests-clar/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 b/tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 rename to tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 diff --git a/tests-clar/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 b/tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 rename to tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 diff --git a/tests-clar/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad b/tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad rename to tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad diff --git a/tests-clar/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 b/tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 rename to tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 diff --git a/tests-clar/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 b/tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 rename to tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 diff --git a/tests-clar/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b b/tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b rename to tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b diff --git a/tests-clar/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d b/tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d rename to tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d diff --git a/tests-clar/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 b/tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 rename to tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 diff --git a/tests-clar/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 b/tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 rename to tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 diff --git a/tests-clar/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 b/tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 rename to tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 diff --git a/tests-clar/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 b/tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 rename to tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 diff --git a/tests-clar/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 b/tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 rename to tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 diff --git a/tests-clar/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 b/tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 rename to tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 diff --git a/tests-clar/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb b/tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb rename to tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb diff --git a/tests-clar/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 b/tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 rename to tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 diff --git a/tests-clar/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 b/tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 rename to tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 diff --git a/tests-clar/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c b/tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c rename to tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c diff --git a/tests-clar/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 b/tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 rename to tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 diff --git a/tests-clar/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b b/tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b similarity index 100% rename from tests-clar/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b rename to tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b diff --git a/tests-clar/resources/submod2/.gitted/refs/heads/master b/tests/resources/submod2/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/.gitted/refs/heads/master rename to tests/resources/submod2/.gitted/refs/heads/master diff --git a/tests-clar/resources/submod2/README.txt b/tests/resources/submod2/README.txt similarity index 100% rename from tests-clar/resources/submod2/README.txt rename to tests/resources/submod2/README.txt diff --git a/tests-clar/resources/submod2/gitmodules b/tests/resources/submod2/gitmodules similarity index 100% rename from tests-clar/resources/submod2/gitmodules rename to tests/resources/submod2/gitmodules diff --git a/tests-clar/resources/submod2/just_a_dir/contents b/tests/resources/submod2/just_a_dir/contents similarity index 100% rename from tests-clar/resources/submod2/just_a_dir/contents rename to tests/resources/submod2/just_a_dir/contents diff --git a/tests-clar/resources/submod2/just_a_file b/tests/resources/submod2/just_a_file similarity index 100% rename from tests-clar/resources/submod2/just_a_file rename to tests/resources/submod2/just_a_file diff --git a/tests-clar/resources/submod2_target/.gitted/HEAD b/tests/resources/submod2/not-submodule/.gitted/HEAD similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/HEAD rename to tests/resources/submod2/not-submodule/.gitted/HEAD diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/config b/tests/resources/submod2/not-submodule/.gitted/config similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/config rename to tests/resources/submod2/not-submodule/.gitted/config diff --git a/tests-clar/resources/submod2_target/.gitted/description b/tests/resources/submod2/not-submodule/.gitted/description similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/description rename to tests/resources/submod2/not-submodule/.gitted/description diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/index b/tests/resources/submod2/not-submodule/.gitted/index similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/index rename to tests/resources/submod2/not-submodule/.gitted/index diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/info/exclude b/tests/resources/submod2/not-submodule/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/info/exclude rename to tests/resources/submod2/not-submodule/.gitted/info/exclude diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/logs/HEAD b/tests/resources/submod2/not-submodule/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/logs/HEAD rename to tests/resources/submod2/not-submodule/.gitted/logs/HEAD diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/logs/refs/heads/master b/tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/logs/refs/heads/master rename to tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 b/tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 rename to tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 b/tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 rename to tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e b/tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e rename to tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e diff --git a/tests-clar/resources/submod2/not-submodule/.gitted/refs/heads/master b/tests/resources/submod2/not-submodule/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2/not-submodule/.gitted/refs/heads/master rename to tests/resources/submod2/not-submodule/.gitted/refs/heads/master diff --git a/tests-clar/resources/submod2/not-submodule/README.txt b/tests/resources/submod2/not-submodule/README.txt similarity index 100% rename from tests-clar/resources/submod2/not-submodule/README.txt rename to tests/resources/submod2/not-submodule/README.txt diff --git a/tests-clar/resources/submod2/not/.gitted/notempty b/tests/resources/submod2/not/.gitted/notempty similarity index 100% rename from tests-clar/resources/submod2/not/.gitted/notempty rename to tests/resources/submod2/not/.gitted/notempty diff --git a/tests-clar/resources/submod2/not/README.txt b/tests/resources/submod2/not/README.txt similarity index 100% rename from tests-clar/resources/submod2/not/README.txt rename to tests/resources/submod2/not/README.txt diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/.gitted b/tests/resources/submod2/sm_added_and_uncommited/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_added_and_uncommited/.gitted rename to tests/resources/submod2/sm_added_and_uncommited/.gitted diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/README.txt b/tests/resources/submod2/sm_added_and_uncommited/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_added_and_uncommited/README.txt rename to tests/resources/submod2/sm_added_and_uncommited/README.txt diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/file_to_modify b/tests/resources/submod2/sm_added_and_uncommited/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_added_and_uncommited/file_to_modify rename to tests/resources/submod2/sm_added_and_uncommited/file_to_modify diff --git a/tests-clar/resources/submod2/sm_changed_file/.gitted b/tests/resources/submod2/sm_changed_file/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_changed_file/.gitted rename to tests/resources/submod2/sm_changed_file/.gitted diff --git a/tests-clar/resources/submod2/sm_changed_file/README.txt b/tests/resources/submod2/sm_changed_file/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_changed_file/README.txt rename to tests/resources/submod2/sm_changed_file/README.txt diff --git a/tests-clar/resources/submod2/sm_changed_file/file_to_modify b/tests/resources/submod2/sm_changed_file/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_changed_file/file_to_modify rename to tests/resources/submod2/sm_changed_file/file_to_modify diff --git a/tests-clar/resources/submod2/sm_changed_head/.gitted b/tests/resources/submod2/sm_changed_head/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_changed_head/.gitted rename to tests/resources/submod2/sm_changed_head/.gitted diff --git a/tests-clar/resources/submod2/sm_changed_head/README.txt b/tests/resources/submod2/sm_changed_head/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_changed_head/README.txt rename to tests/resources/submod2/sm_changed_head/README.txt diff --git a/tests-clar/resources/submod2/sm_changed_head/file_to_modify b/tests/resources/submod2/sm_changed_head/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_changed_head/file_to_modify rename to tests/resources/submod2/sm_changed_head/file_to_modify diff --git a/tests-clar/resources/submod2/sm_changed_index/.gitted b/tests/resources/submod2/sm_changed_index/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_changed_index/.gitted rename to tests/resources/submod2/sm_changed_index/.gitted diff --git a/tests-clar/resources/submod2/sm_changed_index/README.txt b/tests/resources/submod2/sm_changed_index/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_changed_index/README.txt rename to tests/resources/submod2/sm_changed_index/README.txt diff --git a/tests-clar/resources/submod2/sm_changed_index/file_to_modify b/tests/resources/submod2/sm_changed_index/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_changed_index/file_to_modify rename to tests/resources/submod2/sm_changed_index/file_to_modify diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/.gitted b/tests/resources/submod2/sm_changed_untracked_file/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_changed_untracked_file/.gitted rename to tests/resources/submod2/sm_changed_untracked_file/.gitted diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/README.txt b/tests/resources/submod2/sm_changed_untracked_file/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_changed_untracked_file/README.txt rename to tests/resources/submod2/sm_changed_untracked_file/README.txt diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/file_to_modify b/tests/resources/submod2/sm_changed_untracked_file/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_changed_untracked_file/file_to_modify rename to tests/resources/submod2/sm_changed_untracked_file/file_to_modify diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/i_am_untracked b/tests/resources/submod2/sm_changed_untracked_file/i_am_untracked similarity index 100% rename from tests-clar/resources/submod2/sm_changed_untracked_file/i_am_untracked rename to tests/resources/submod2/sm_changed_untracked_file/i_am_untracked diff --git a/tests-clar/resources/submod2/sm_missing_commits/.gitted b/tests/resources/submod2/sm_missing_commits/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_missing_commits/.gitted rename to tests/resources/submod2/sm_missing_commits/.gitted diff --git a/tests-clar/resources/submod2/sm_missing_commits/README.txt b/tests/resources/submod2/sm_missing_commits/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_missing_commits/README.txt rename to tests/resources/submod2/sm_missing_commits/README.txt diff --git a/tests-clar/resources/submod2/sm_missing_commits/file_to_modify b/tests/resources/submod2/sm_missing_commits/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_missing_commits/file_to_modify rename to tests/resources/submod2/sm_missing_commits/file_to_modify diff --git a/tests-clar/resources/submod2/sm_unchanged/.gitted b/tests/resources/submod2/sm_unchanged/.gitted similarity index 100% rename from tests-clar/resources/submod2/sm_unchanged/.gitted rename to tests/resources/submod2/sm_unchanged/.gitted diff --git a/tests-clar/resources/submod2/sm_unchanged/README.txt b/tests/resources/submod2/sm_unchanged/README.txt similarity index 100% rename from tests-clar/resources/submod2/sm_unchanged/README.txt rename to tests/resources/submod2/sm_unchanged/README.txt diff --git a/tests-clar/resources/submod2/sm_unchanged/file_to_modify b/tests/resources/submod2/sm_unchanged/file_to_modify similarity index 100% rename from tests-clar/resources/submod2/sm_unchanged/file_to_modify rename to tests/resources/submod2/sm_unchanged/file_to_modify diff --git a/tests-clar/resources/submodules/.gitted/HEAD b/tests/resources/submod2_target/.gitted/HEAD similarity index 100% rename from tests-clar/resources/submodules/.gitted/HEAD rename to tests/resources/submod2_target/.gitted/HEAD diff --git a/tests-clar/resources/submod2_target/.gitted/config b/tests/resources/submod2_target/.gitted/config similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/config rename to tests/resources/submod2_target/.gitted/config diff --git a/tests-clar/resources/submodules/.gitted/description b/tests/resources/submod2_target/.gitted/description similarity index 100% rename from tests-clar/resources/submodules/.gitted/description rename to tests/resources/submod2_target/.gitted/description diff --git a/tests-clar/resources/submod2_target/.gitted/index b/tests/resources/submod2_target/.gitted/index similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/index rename to tests/resources/submod2_target/.gitted/index diff --git a/tests-clar/resources/submod2_target/.gitted/info/exclude b/tests/resources/submod2_target/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/info/exclude rename to tests/resources/submod2_target/.gitted/info/exclude diff --git a/tests-clar/resources/submod2_target/.gitted/logs/HEAD b/tests/resources/submod2_target/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/logs/HEAD rename to tests/resources/submod2_target/.gitted/logs/HEAD diff --git a/tests-clar/resources/submod2_target/.gitted/logs/refs/heads/master b/tests/resources/submod2_target/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/logs/refs/heads/master rename to tests/resources/submod2_target/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/submod2_target/.gitted/refs/heads/master b/tests/resources/submod2_target/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/submod2_target/.gitted/refs/heads/master rename to tests/resources/submod2_target/.gitted/refs/heads/master diff --git a/tests-clar/resources/submod2_target/README.txt b/tests/resources/submod2_target/README.txt similarity index 100% rename from tests-clar/resources/submod2_target/README.txt rename to tests/resources/submod2_target/README.txt diff --git a/tests-clar/resources/submod2_target/file_to_modify b/tests/resources/submod2_target/file_to_modify similarity index 100% rename from tests-clar/resources/submod2_target/file_to_modify rename to tests/resources/submod2_target/file_to_modify diff --git a/tests-clar/resources/submodules/testrepo/.gitted/HEAD b/tests/resources/submodules/.gitted/HEAD similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/HEAD rename to tests/resources/submodules/.gitted/HEAD diff --git a/tests-clar/resources/submodules/.gitted/config b/tests/resources/submodules/.gitted/config similarity index 100% rename from tests-clar/resources/submodules/.gitted/config rename to tests/resources/submodules/.gitted/config diff --git a/tests-clar/resources/submodules/testrepo/.gitted/description b/tests/resources/submodules/.gitted/description similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/description rename to tests/resources/submodules/.gitted/description diff --git a/tests-clar/resources/submodules/.gitted/index b/tests/resources/submodules/.gitted/index similarity index 100% rename from tests-clar/resources/submodules/.gitted/index rename to tests/resources/submodules/.gitted/index diff --git a/tests-clar/resources/submodules/.gitted/info/exclude b/tests/resources/submodules/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/submodules/.gitted/info/exclude rename to tests/resources/submodules/.gitted/info/exclude diff --git a/tests-clar/resources/submodules/.gitted/info/refs b/tests/resources/submodules/.gitted/info/refs similarity index 100% rename from tests-clar/resources/submodules/.gitted/info/refs rename to tests/resources/submodules/.gitted/info/refs diff --git a/tests-clar/resources/submodules/.gitted/logs/HEAD b/tests/resources/submodules/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/submodules/.gitted/logs/HEAD rename to tests/resources/submodules/.gitted/logs/HEAD diff --git a/tests-clar/resources/submodules/.gitted/logs/refs/heads/master b/tests/resources/submodules/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submodules/.gitted/logs/refs/heads/master rename to tests/resources/submodules/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e b/tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e rename to tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e diff --git a/tests-clar/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 b/tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 rename to tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 diff --git a/tests-clar/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 b/tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 rename to tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 diff --git a/tests-clar/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 b/tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 rename to tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 diff --git a/tests-clar/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 b/tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 rename to tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 diff --git a/tests-clar/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae b/tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae rename to tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae diff --git a/tests-clar/resources/submodules/.gitted/objects/info/packs b/tests/resources/submodules/.gitted/objects/info/packs similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/info/packs rename to tests/resources/submodules/.gitted/objects/info/packs diff --git a/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx rename to tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx diff --git a/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack similarity index 100% rename from tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack rename to tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack diff --git a/tests-clar/resources/submodules/.gitted/packed-refs b/tests/resources/submodules/.gitted/packed-refs similarity index 100% rename from tests-clar/resources/submodules/.gitted/packed-refs rename to tests/resources/submodules/.gitted/packed-refs diff --git a/tests-clar/resources/submodules/.gitted/refs/heads/master b/tests/resources/submodules/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/submodules/.gitted/refs/heads/master rename to tests/resources/submodules/.gitted/refs/heads/master diff --git a/tests-clar/resources/submodules/added b/tests/resources/submodules/added similarity index 100% rename from tests-clar/resources/submodules/added rename to tests/resources/submodules/added diff --git a/tests-clar/resources/submodules/gitmodules b/tests/resources/submodules/gitmodules similarity index 54% rename from tests-clar/resources/submodules/gitmodules rename to tests/resources/submodules/gitmodules index 1262f8bb0..2798b696c 100644 Binary files a/tests-clar/resources/submodules/gitmodules and b/tests/resources/submodules/gitmodules differ diff --git a/tests-clar/resources/submodules/ignored b/tests/resources/submodules/ignored similarity index 100% rename from tests-clar/resources/submodules/ignored rename to tests/resources/submodules/ignored diff --git a/tests-clar/resources/submodules/modified b/tests/resources/submodules/modified similarity index 100% rename from tests-clar/resources/submodules/modified rename to tests/resources/submodules/modified diff --git a/tests-clar/resources/testrepo.git/HEAD b/tests/resources/submodules/testrepo/.gitted/HEAD similarity index 100% rename from tests-clar/resources/testrepo.git/HEAD rename to tests/resources/submodules/testrepo/.gitted/HEAD diff --git a/tests-clar/resources/submodules/testrepo/.gitted/config b/tests/resources/submodules/testrepo/.gitted/config similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/config rename to tests/resources/submodules/testrepo/.gitted/config diff --git a/tests-clar/resources/testrepo2/.gitted/description b/tests/resources/submodules/testrepo/.gitted/description similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/description rename to tests/resources/submodules/testrepo/.gitted/description diff --git a/tests-clar/resources/submodules/testrepo/.gitted/index b/tests/resources/submodules/testrepo/.gitted/index similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/index rename to tests/resources/submodules/testrepo/.gitted/index diff --git a/tests-clar/resources/submodules/testrepo/.gitted/info/exclude b/tests/resources/submodules/testrepo/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/info/exclude rename to tests/resources/submodules/testrepo/.gitted/info/exclude diff --git a/tests-clar/resources/submodules/testrepo/.gitted/logs/HEAD b/tests/resources/submodules/testrepo/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/logs/HEAD rename to tests/resources/submodules/testrepo/.gitted/logs/HEAD diff --git a/tests-clar/resources/submodules/testrepo/.gitted/logs/refs/heads/master b/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/logs/refs/heads/master rename to tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b rename to tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d rename to tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 rename to tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc rename to tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af rename to tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 rename to tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 rename to tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 rename to tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 rename to tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 rename to tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 rename to tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 rename to tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 rename to tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f rename to tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 rename to tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 rename to tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack rename to tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack diff --git a/tests-clar/resources/submodules/testrepo/.gitted/packed-refs b/tests/resources/submodules/testrepo/.gitted/packed-refs similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/packed-refs rename to tests/resources/submodules/testrepo/.gitted/packed-refs diff --git a/tests-clar/resources/submodules/testrepo/.gitted/refs/heads/master b/tests/resources/submodules/testrepo/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/refs/heads/master rename to tests/resources/submodules/testrepo/.gitted/refs/heads/master diff --git a/tests-clar/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD b/tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD rename to tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/submodules/testrepo/README b/tests/resources/submodules/testrepo/README similarity index 100% rename from tests-clar/resources/submodules/testrepo/README rename to tests/resources/submodules/testrepo/README diff --git a/tests-clar/resources/submodules/testrepo/branch_file.txt b/tests/resources/submodules/testrepo/branch_file.txt similarity index 100% rename from tests-clar/resources/submodules/testrepo/branch_file.txt rename to tests/resources/submodules/testrepo/branch_file.txt diff --git a/tests-clar/resources/submodules/testrepo/new.txt b/tests/resources/submodules/testrepo/new.txt similarity index 100% rename from tests-clar/resources/submodules/testrepo/new.txt rename to tests/resources/submodules/testrepo/new.txt diff --git a/tests-clar/resources/submodules/unmodified b/tests/resources/submodules/unmodified similarity index 100% rename from tests-clar/resources/submodules/unmodified rename to tests/resources/submodules/unmodified diff --git a/tests-clar/resources/submodules/untracked b/tests/resources/submodules/untracked similarity index 100% rename from tests-clar/resources/submodules/untracked rename to tests/resources/submodules/untracked diff --git a/tests-clar/resources/template/branches/.gitignore b/tests/resources/template/branches/.gitignore similarity index 100% rename from tests-clar/resources/template/branches/.gitignore rename to tests/resources/template/branches/.gitignore diff --git a/tests-clar/resources/template/description b/tests/resources/template/description similarity index 100% rename from tests-clar/resources/template/description rename to tests/resources/template/description diff --git a/tests-clar/resources/template/hooks/link.sample b/tests/resources/template/hooks/link.sample similarity index 100% rename from tests-clar/resources/template/hooks/link.sample rename to tests/resources/template/hooks/link.sample diff --git a/tests-clar/resources/template/hooks/update.sample b/tests/resources/template/hooks/update.sample similarity index 100% rename from tests-clar/resources/template/hooks/update.sample rename to tests/resources/template/hooks/update.sample diff --git a/tests-clar/resources/template/info/exclude b/tests/resources/template/info/exclude similarity index 100% rename from tests-clar/resources/template/info/exclude rename to tests/resources/template/info/exclude diff --git a/tests-clar/resources/testrepo.git/FETCH_HEAD b/tests/resources/testrepo.git/FETCH_HEAD similarity index 100% rename from tests-clar/resources/testrepo.git/FETCH_HEAD rename to tests/resources/testrepo.git/FETCH_HEAD diff --git a/tests-clar/resources/testrepo/.gitted/HEAD b/tests/resources/testrepo.git/HEAD similarity index 100% rename from tests-clar/resources/testrepo/.gitted/HEAD rename to tests/resources/testrepo.git/HEAD diff --git a/tests-clar/resources/testrepo.git/HEAD_TRACKER b/tests/resources/testrepo.git/HEAD_TRACKER similarity index 100% rename from tests-clar/resources/testrepo.git/HEAD_TRACKER rename to tests/resources/testrepo.git/HEAD_TRACKER diff --git a/tests-clar/resources/testrepo.git/config b/tests/resources/testrepo.git/config similarity index 90% rename from tests-clar/resources/testrepo.git/config rename to tests/resources/testrepo.git/config index 904a4e3f3..dfab4eeb4 100644 Binary files a/tests-clar/resources/testrepo.git/config and b/tests/resources/testrepo.git/config differ diff --git a/tests-clar/resources/testrepo.git/index b/tests/resources/testrepo.git/index similarity index 100% rename from tests-clar/resources/testrepo.git/index rename to tests/resources/testrepo.git/index diff --git a/tests-clar/resources/testrepo.git/logs/HEAD b/tests/resources/testrepo.git/logs/HEAD similarity index 100% rename from tests-clar/resources/testrepo.git/logs/HEAD rename to tests/resources/testrepo.git/logs/HEAD diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/br2 b/tests/resources/testrepo.git/logs/refs/heads/br2 similarity index 100% rename from tests-clar/resources/testrepo.git/logs/refs/heads/br2 rename to tests/resources/testrepo.git/logs/refs/heads/br2 diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/master b/tests/resources/testrepo.git/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/testrepo.git/logs/refs/heads/master rename to tests/resources/testrepo.git/logs/refs/heads/master diff --git a/tests-clar/resources/testrepo.git/logs/refs/heads/not-good b/tests/resources/testrepo.git/logs/refs/heads/not-good similarity index 100% rename from tests-clar/resources/testrepo.git/logs/refs/heads/not-good rename to tests/resources/testrepo.git/logs/refs/heads/not-good diff --git a/tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD b/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/testrepo.git/logs/refs/remotes/origin/HEAD rename to tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master b/tests/resources/testrepo.git/logs/refs/remotes/test/master similarity index 100% rename from tests-clar/resources/testrepo.git/logs/refs/remotes/test/master rename to tests/resources/testrepo.git/logs/refs/remotes/test/master diff --git a/tests-clar/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 rename to tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 diff --git a/tests-clar/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd similarity index 100% rename from tests-clar/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd rename to tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd diff --git a/tests-clar/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 rename to tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 diff --git a/tests-clar/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b similarity index 100% rename from tests-clar/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b rename to tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b diff --git a/tests-clar/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 rename to tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 diff --git a/tests-clar/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d similarity index 100% rename from tests-clar/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d rename to tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d diff --git a/tests-clar/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 rename to tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 diff --git a/tests-clar/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 rename to tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 diff --git a/tests-clar/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc similarity index 100% rename from tests-clar/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc rename to tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc diff --git a/tests-clar/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 b/tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 rename to tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 diff --git a/tests-clar/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea similarity index 100% rename from tests-clar/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea rename to tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea diff --git a/tests-clar/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 rename to tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 diff --git a/tests-clar/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af similarity index 100% rename from tests-clar/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af rename to tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af diff --git a/tests-clar/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 rename to tests/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 diff --git a/tests-clar/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe similarity index 100% rename from tests-clar/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe rename to tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe diff --git a/tests-clar/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 rename to tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 diff --git a/tests-clar/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 rename to tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 diff --git a/tests-clar/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 rename to tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 diff --git a/tests-clar/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 rename to tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 diff --git a/tests-clar/resources/testrepo.git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/testrepo.git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/testrepo.git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/testrepo.git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/testrepo.git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/testrepo.git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/testrepo.git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 rename to tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 diff --git a/tests-clar/resources/testrepo.git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/testrepo.git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 rename to tests/resources/testrepo.git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 diff --git a/tests-clar/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 rename to tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 diff --git a/tests-clar/resources/testrepo.git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/testrepo.git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 rename to tests/resources/testrepo.git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 diff --git a/tests-clar/resources/testrepo.git/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/testrepo.git/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/testrepo.git/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/testrepo.git/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 b/tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 rename to tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 diff --git a/tests-clar/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f similarity index 100% rename from tests-clar/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f rename to tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f diff --git a/tests-clar/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 b/tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 rename to tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 diff --git a/tests-clar/resources/testrepo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/testrepo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/testrepo.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 rename to tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 diff --git a/tests-clar/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 rename to tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 diff --git a/tests-clar/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/testrepo.git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/testrepo.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/testrepo.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/testrepo.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/testrepo.git/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/testrepo.git/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/testrepo.git/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 b/tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 similarity index 100% rename from tests-clar/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 rename to tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx rename to tests/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack rename to tests/resources/testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx rename to tests/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack rename to tests/resources/testrepo.git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx rename to tests/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx diff --git a/tests-clar/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack similarity index 100% rename from tests-clar/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack rename to tests/resources/testrepo.git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack diff --git a/tests-clar/resources/testrepo.git/packed-refs b/tests/resources/testrepo.git/packed-refs similarity index 100% rename from tests-clar/resources/testrepo.git/packed-refs rename to tests/resources/testrepo.git/packed-refs diff --git a/tests-clar/resources/testrepo.git/refs/heads/br2 b/tests/resources/testrepo.git/refs/heads/br2 similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/br2 rename to tests/resources/testrepo.git/refs/heads/br2 diff --git a/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch b/tests/resources/testrepo.git/refs/heads/cannot-fetch similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/cannot-fetch rename to tests/resources/testrepo.git/refs/heads/cannot-fetch diff --git a/tests-clar/resources/testrepo.git/refs/heads/chomped b/tests/resources/testrepo.git/refs/heads/chomped similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/chomped rename to tests/resources/testrepo.git/refs/heads/chomped diff --git a/tests-clar/resources/testrepo.git/refs/heads/haacked b/tests/resources/testrepo.git/refs/heads/haacked similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/haacked rename to tests/resources/testrepo.git/refs/heads/haacked diff --git a/tests-clar/resources/testrepo.git/refs/heads/master b/tests/resources/testrepo.git/refs/heads/master similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/master rename to tests/resources/testrepo.git/refs/heads/master diff --git a/tests-clar/resources/testrepo.git/refs/heads/not-good b/tests/resources/testrepo.git/refs/heads/not-good similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/not-good rename to tests/resources/testrepo.git/refs/heads/not-good diff --git a/tests-clar/resources/testrepo.git/refs/heads/packed-test b/tests/resources/testrepo.git/refs/heads/packed-test similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/packed-test rename to tests/resources/testrepo.git/refs/heads/packed-test diff --git a/tests-clar/resources/testrepo.git/refs/heads/subtrees b/tests/resources/testrepo.git/refs/heads/subtrees similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/subtrees rename to tests/resources/testrepo.git/refs/heads/subtrees diff --git a/tests-clar/resources/testrepo.git/refs/heads/test b/tests/resources/testrepo.git/refs/heads/test similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/test rename to tests/resources/testrepo.git/refs/heads/test diff --git a/tests-clar/resources/testrepo.git/refs/heads/track-local b/tests/resources/testrepo.git/refs/heads/track-local similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/track-local rename to tests/resources/testrepo.git/refs/heads/track-local diff --git a/tests-clar/resources/testrepo.git/refs/heads/trailing b/tests/resources/testrepo.git/refs/heads/trailing similarity index 100% rename from tests-clar/resources/testrepo.git/refs/heads/trailing rename to tests/resources/testrepo.git/refs/heads/trailing diff --git a/tests-clar/resources/testrepo.git/refs/notes/fanout b/tests/resources/testrepo.git/refs/notes/fanout similarity index 100% rename from tests-clar/resources/testrepo.git/refs/notes/fanout rename to tests/resources/testrepo.git/refs/notes/fanout diff --git a/tests-clar/resources/testrepo.git/refs/remotes/test/master b/tests/resources/testrepo.git/refs/remotes/test/master similarity index 100% rename from tests-clar/resources/testrepo.git/refs/remotes/test/master rename to tests/resources/testrepo.git/refs/remotes/test/master diff --git a/tests-clar/resources/testrepo.git/refs/tags/annotated_tag_to_blob b/tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/annotated_tag_to_blob rename to tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob diff --git a/tests-clar/resources/testrepo.git/refs/tags/e90810b b/tests/resources/testrepo.git/refs/tags/e90810b similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/e90810b rename to tests/resources/testrepo.git/refs/tags/e90810b diff --git a/tests-clar/resources/testrepo.git/refs/tags/hard_tag b/tests/resources/testrepo.git/refs/tags/hard_tag similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/hard_tag rename to tests/resources/testrepo.git/refs/tags/hard_tag diff --git a/tests-clar/resources/testrepo.git/refs/tags/point_to_blob b/tests/resources/testrepo.git/refs/tags/point_to_blob similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/point_to_blob rename to tests/resources/testrepo.git/refs/tags/point_to_blob diff --git a/tests-clar/resources/testrepo.git/refs/tags/taggerless b/tests/resources/testrepo.git/refs/tags/taggerless similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/taggerless rename to tests/resources/testrepo.git/refs/tags/taggerless diff --git a/tests-clar/resources/testrepo.git/refs/tags/test b/tests/resources/testrepo.git/refs/tags/test similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/test rename to tests/resources/testrepo.git/refs/tags/test diff --git a/tests-clar/resources/testrepo.git/refs/tags/wrapped_tag b/tests/resources/testrepo.git/refs/tags/wrapped_tag similarity index 100% rename from tests-clar/resources/testrepo.git/refs/tags/wrapped_tag rename to tests/resources/testrepo.git/refs/tags/wrapped_tag diff --git a/tests-clar/resources/testrepo2/.gitted/HEAD b/tests/resources/testrepo/.gitted/HEAD similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/HEAD rename to tests/resources/testrepo/.gitted/HEAD diff --git a/tests-clar/resources/testrepo/.gitted/HEAD_TRACKER b/tests/resources/testrepo/.gitted/HEAD_TRACKER similarity index 100% rename from tests-clar/resources/testrepo/.gitted/HEAD_TRACKER rename to tests/resources/testrepo/.gitted/HEAD_TRACKER diff --git a/tests-clar/resources/testrepo/.gitted/config b/tests/resources/testrepo/.gitted/config similarity index 100% rename from tests-clar/resources/testrepo/.gitted/config rename to tests/resources/testrepo/.gitted/config diff --git a/tests-clar/resources/testrepo/.gitted/index b/tests/resources/testrepo/.gitted/index similarity index 100% rename from tests-clar/resources/testrepo/.gitted/index rename to tests/resources/testrepo/.gitted/index diff --git a/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff b/tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff rename to tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff diff --git a/tests-clar/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e rename to tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 rename to tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 diff --git a/tests-clar/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b rename to tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b diff --git a/tests-clar/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d rename to tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d diff --git a/tests-clar/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 rename to tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 diff --git a/tests-clar/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc rename to tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc diff --git a/tests-clar/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 b/tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 rename to tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 diff --git a/tests-clar/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 rename to tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 diff --git a/tests-clar/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc rename to tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc diff --git a/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 rename to tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 diff --git a/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 b/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 new file mode 100644 index 000000000..ee7c78174 Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 differ diff --git a/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e b/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e new file mode 100644 index 000000000..197685b86 Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af rename to tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af diff --git a/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a b/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a new file mode 100644 index 000000000..db778aaae Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 rename to tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 diff --git a/tests-clar/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 b/tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 rename to tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 diff --git a/tests-clar/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 rename to tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 diff --git a/tests-clar/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 rename to tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 diff --git a/tests-clar/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 rename to tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 diff --git a/tests-clar/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 rename to tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 diff --git a/tests-clar/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 rename to tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 diff --git a/tests-clar/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 rename to tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 diff --git a/tests-clar/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 rename to tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 diff --git a/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e b/tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e rename to tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e diff --git a/tests-clar/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 rename to tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 diff --git a/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 rename to tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 diff --git a/tests-clar/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f rename to tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f diff --git a/tests-clar/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 rename to tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 diff --git a/tests-clar/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 rename to tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 diff --git a/tests-clar/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx rename to tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack rename to tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx rename to tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack rename to tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx rename to tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx diff --git a/tests-clar/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack similarity index 100% rename from tests-clar/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack rename to tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack diff --git a/tests-clar/resources/testrepo/.gitted/packed-refs b/tests/resources/testrepo/.gitted/packed-refs similarity index 100% rename from tests-clar/resources/testrepo/.gitted/packed-refs rename to tests/resources/testrepo/.gitted/packed-refs diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/br2 b/tests/resources/testrepo/.gitted/refs/heads/br2 similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/br2 rename to tests/resources/testrepo/.gitted/refs/heads/br2 diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/dir b/tests/resources/testrepo/.gitted/refs/heads/dir similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/dir rename to tests/resources/testrepo/.gitted/refs/heads/dir diff --git a/tests/resources/testrepo/.gitted/refs/heads/long-file-name b/tests/resources/testrepo/.gitted/refs/heads/long-file-name new file mode 100644 index 000000000..1f942a746 Binary files /dev/null and b/tests/resources/testrepo/.gitted/refs/heads/long-file-name differ diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/master b/tests/resources/testrepo/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/master rename to tests/resources/testrepo/.gitted/refs/heads/master diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/packed-test b/tests/resources/testrepo/.gitted/refs/heads/packed-test similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/packed-test rename to tests/resources/testrepo/.gitted/refs/heads/packed-test diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/subtrees b/tests/resources/testrepo/.gitted/refs/heads/subtrees similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/subtrees rename to tests/resources/testrepo/.gitted/refs/heads/subtrees diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/test b/tests/resources/testrepo/.gitted/refs/heads/test similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/heads/test rename to tests/resources/testrepo/.gitted/refs/heads/test diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/e90810b b/tests/resources/testrepo/.gitted/refs/tags/e90810b similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/tags/e90810b rename to tests/resources/testrepo/.gitted/refs/tags/e90810b diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar b/tests/resources/testrepo/.gitted/refs/tags/foo/bar similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/tags/foo/bar rename to tests/resources/testrepo/.gitted/refs/tags/foo/bar diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar b/tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/tags/foo/foo/bar rename to tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/point_to_blob b/tests/resources/testrepo/.gitted/refs/tags/point_to_blob similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/tags/point_to_blob rename to tests/resources/testrepo/.gitted/refs/tags/point_to_blob diff --git a/tests-clar/resources/testrepo/.gitted/refs/tags/test b/tests/resources/testrepo/.gitted/refs/tags/test similarity index 100% rename from tests-clar/resources/testrepo/.gitted/refs/tags/test rename to tests/resources/testrepo/.gitted/refs/tags/test diff --git a/tests-clar/resources/twowaymerge.git/HEAD b/tests/resources/testrepo2/.gitted/HEAD similarity index 100% rename from tests-clar/resources/twowaymerge.git/HEAD rename to tests/resources/testrepo2/.gitted/HEAD diff --git a/tests-clar/resources/testrepo2/.gitted/config b/tests/resources/testrepo2/.gitted/config similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/config rename to tests/resources/testrepo2/.gitted/config diff --git a/tests-clar/resources/twowaymerge.git/description b/tests/resources/testrepo2/.gitted/description similarity index 100% rename from tests-clar/resources/twowaymerge.git/description rename to tests/resources/testrepo2/.gitted/description diff --git a/tests-clar/resources/testrepo2/.gitted/index b/tests/resources/testrepo2/.gitted/index similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/index rename to tests/resources/testrepo2/.gitted/index diff --git a/tests-clar/resources/testrepo2/.gitted/info/exclude b/tests/resources/testrepo2/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/info/exclude rename to tests/resources/testrepo2/.gitted/info/exclude diff --git a/tests-clar/resources/testrepo2/.gitted/logs/HEAD b/tests/resources/testrepo2/.gitted/logs/HEAD similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/logs/HEAD rename to tests/resources/testrepo2/.gitted/logs/HEAD diff --git a/tests-clar/resources/testrepo2/.gitted/logs/refs/heads/master b/tests/resources/testrepo2/.gitted/logs/refs/heads/master similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/logs/refs/heads/master rename to tests/resources/testrepo2/.gitted/logs/refs/heads/master diff --git a/tests-clar/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD b/tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD rename to tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d b/tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d rename to tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d diff --git a/tests-clar/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2 b/tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2 rename to tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a b/tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a rename to tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a diff --git a/tests-clar/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96 b/tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96 rename to tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0 b/tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0 rename to tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd rename to tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 rename to tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 rename to tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd rename to tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd diff --git a/tests-clar/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b b/tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b rename to tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b diff --git a/tests-clar/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 rename to tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 rename to tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 rename to tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx rename to tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx diff --git a/tests-clar/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack rename to tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack diff --git a/tests-clar/resources/testrepo2/.gitted/packed-refs b/tests/resources/testrepo2/.gitted/packed-refs similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/packed-refs rename to tests/resources/testrepo2/.gitted/packed-refs diff --git a/tests-clar/resources/testrepo2/.gitted/refs/heads/master b/tests/resources/testrepo2/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/refs/heads/master rename to tests/resources/testrepo2/.gitted/refs/heads/master diff --git a/tests-clar/resources/testrepo2/.gitted/refs/remotes/origin/HEAD b/tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/testrepo2/.gitted/refs/remotes/origin/HEAD rename to tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/testrepo2/README b/tests/resources/testrepo2/README similarity index 100% rename from tests-clar/resources/testrepo2/README rename to tests/resources/testrepo2/README diff --git a/tests-clar/resources/testrepo2/new.txt b/tests/resources/testrepo2/new.txt similarity index 100% rename from tests-clar/resources/testrepo2/new.txt rename to tests/resources/testrepo2/new.txt diff --git a/tests-clar/resources/testrepo2/subdir/README b/tests/resources/testrepo2/subdir/README similarity index 100% rename from tests-clar/resources/testrepo2/subdir/README rename to tests/resources/testrepo2/subdir/README diff --git a/tests-clar/resources/testrepo2/subdir/new.txt b/tests/resources/testrepo2/subdir/new.txt similarity index 100% rename from tests-clar/resources/testrepo2/subdir/new.txt rename to tests/resources/testrepo2/subdir/new.txt diff --git a/tests-clar/resources/testrepo2/subdir/subdir2/README b/tests/resources/testrepo2/subdir/subdir2/README similarity index 100% rename from tests-clar/resources/testrepo2/subdir/subdir2/README rename to tests/resources/testrepo2/subdir/subdir2/README diff --git a/tests-clar/resources/testrepo2/subdir/subdir2/new.txt b/tests/resources/testrepo2/subdir/subdir2/new.txt similarity index 100% rename from tests-clar/resources/testrepo2/subdir/subdir2/new.txt rename to tests/resources/testrepo2/subdir/subdir2/new.txt diff --git a/tests-clar/resources/typechanges/.gitted/HEAD b/tests/resources/twowaymerge.git/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/HEAD rename to tests/resources/twowaymerge.git/HEAD diff --git a/tests/resources/twowaymerge.git/config b/tests/resources/twowaymerge.git/config new file mode 100644 index 000000000..c53d818dd Binary files /dev/null and b/tests/resources/twowaymerge.git/config differ diff --git a/tests-clar/resources/typechanges/.gitted/description b/tests/resources/twowaymerge.git/description similarity index 100% rename from tests-clar/resources/typechanges/.gitted/description rename to tests/resources/twowaymerge.git/description diff --git a/tests-clar/resources/twowaymerge.git/info/exclude b/tests/resources/twowaymerge.git/info/exclude similarity index 100% rename from tests-clar/resources/twowaymerge.git/info/exclude rename to tests/resources/twowaymerge.git/info/exclude diff --git a/tests-clar/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 b/tests/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 rename to tests/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 diff --git a/tests-clar/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67 b/tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67 rename to tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67 diff --git a/tests-clar/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5 b/tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5 rename to tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5 diff --git a/tests-clar/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b b/tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b rename to tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b diff --git a/tests-clar/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006 b/tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006 rename to tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006 diff --git a/tests-clar/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83 b/tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83 rename to tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83 diff --git a/tests-clar/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 b/tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 rename to tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 diff --git a/tests-clar/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432 b/tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432 rename to tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432 diff --git a/tests-clar/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8 b/tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8 rename to tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8 diff --git a/tests-clar/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4 b/tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4 rename to tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4 diff --git a/tests-clar/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba b/tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba rename to tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba diff --git a/tests-clar/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118 b/tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118 rename to tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118 diff --git a/tests-clar/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f b/tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f rename to tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f diff --git a/tests-clar/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11 b/tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11 rename to tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11 diff --git a/tests-clar/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912 b/tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912 rename to tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912 diff --git a/tests-clar/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247 b/tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247 rename to tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247 diff --git a/tests-clar/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545 b/tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545 rename to tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545 diff --git a/tests-clar/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650 b/tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650 rename to tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650 diff --git a/tests-clar/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30 b/tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30 rename to tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30 diff --git a/tests-clar/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28 b/tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28 rename to tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28 diff --git a/tests-clar/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706 b/tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706 rename to tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706 diff --git a/tests-clar/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19 b/tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19 rename to tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19 diff --git a/tests-clar/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417 b/tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417 rename to tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417 diff --git a/tests-clar/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c b/tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c rename to tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c diff --git a/tests-clar/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4 b/tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4 rename to tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4 diff --git a/tests-clar/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef b/tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef rename to tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef diff --git a/tests-clar/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6 b/tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6 rename to tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6 diff --git a/tests-clar/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6 b/tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6 rename to tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6 diff --git a/tests-clar/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494 b/tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494 rename to tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494 diff --git a/tests-clar/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e b/tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e rename to tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e diff --git a/tests-clar/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694 b/tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694 rename to tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694 diff --git a/tests-clar/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25 b/tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25 similarity index 100% rename from tests-clar/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25 rename to tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25 diff --git a/tests-clar/resources/twowaymerge.git/refs/heads/first-branch b/tests/resources/twowaymerge.git/refs/heads/first-branch similarity index 100% rename from tests-clar/resources/twowaymerge.git/refs/heads/first-branch rename to tests/resources/twowaymerge.git/refs/heads/first-branch diff --git a/tests-clar/resources/twowaymerge.git/refs/heads/master b/tests/resources/twowaymerge.git/refs/heads/master similarity index 100% rename from tests-clar/resources/twowaymerge.git/refs/heads/master rename to tests/resources/twowaymerge.git/refs/heads/master diff --git a/tests-clar/resources/twowaymerge.git/refs/heads/second-branch b/tests/resources/twowaymerge.git/refs/heads/second-branch similarity index 100% rename from tests-clar/resources/twowaymerge.git/refs/heads/second-branch rename to tests/resources/twowaymerge.git/refs/heads/second-branch diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/HEAD b/tests/resources/typechanges/.gitted/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/HEAD rename to tests/resources/typechanges/.gitted/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/config b/tests/resources/typechanges/.gitted/config similarity index 100% rename from tests-clar/resources/typechanges/.gitted/config rename to tests/resources/typechanges/.gitted/config diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/description b/tests/resources/typechanges/.gitted/description similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/description rename to tests/resources/typechanges/.gitted/description diff --git a/tests-clar/resources/typechanges/.gitted/index b/tests/resources/typechanges/.gitted/index similarity index 100% rename from tests-clar/resources/typechanges/.gitted/index rename to tests/resources/typechanges/.gitted/index diff --git a/tests-clar/resources/typechanges/.gitted/info/exclude b/tests/resources/typechanges/.gitted/info/exclude similarity index 100% rename from tests-clar/resources/typechanges/.gitted/info/exclude rename to tests/resources/typechanges/.gitted/info/exclude diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/HEAD b/tests/resources/typechanges/.gitted/modules/b/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/HEAD rename to tests/resources/typechanges/.gitted/modules/b/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/config b/tests/resources/typechanges/.gitted/modules/b/config similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/config rename to tests/resources/typechanges/.gitted/modules/b/config diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/description b/tests/resources/typechanges/.gitted/modules/b/description similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/description rename to tests/resources/typechanges/.gitted/modules/b/description diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/index b/tests/resources/typechanges/.gitted/modules/b/index similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/index rename to tests/resources/typechanges/.gitted/modules/b/index diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/info/exclude b/tests/resources/typechanges/.gitted/modules/b/info/exclude similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/info/exclude rename to tests/resources/typechanges/.gitted/modules/b/info/exclude diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/packed-refs b/tests/resources/typechanges/.gitted/modules/b/packed-refs similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/packed-refs rename to tests/resources/typechanges/.gitted/modules/b/packed-refs diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/refs/heads/master b/tests/resources/typechanges/.gitted/modules/b/refs/heads/master similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/refs/heads/master rename to tests/resources/typechanges/.gitted/modules/b/refs/heads/master diff --git a/tests-clar/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD rename to tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/HEAD b/tests/resources/typechanges/.gitted/modules/d/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/HEAD rename to tests/resources/typechanges/.gitted/modules/d/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/config b/tests/resources/typechanges/.gitted/modules/d/config similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/config rename to tests/resources/typechanges/.gitted/modules/d/config diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/description b/tests/resources/typechanges/.gitted/modules/d/description similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/description rename to tests/resources/typechanges/.gitted/modules/d/description diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/index b/tests/resources/typechanges/.gitted/modules/d/index similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/index rename to tests/resources/typechanges/.gitted/modules/d/index diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/info/exclude b/tests/resources/typechanges/.gitted/modules/d/info/exclude similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/info/exclude rename to tests/resources/typechanges/.gitted/modules/d/info/exclude diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/packed-refs b/tests/resources/typechanges/.gitted/modules/d/packed-refs similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/packed-refs rename to tests/resources/typechanges/.gitted/modules/d/packed-refs diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/refs/heads/master b/tests/resources/typechanges/.gitted/modules/d/refs/heads/master similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/refs/heads/master rename to tests/resources/typechanges/.gitted/modules/d/refs/heads/master diff --git a/tests-clar/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD rename to tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/unsymlinked.git/HEAD b/tests/resources/typechanges/.gitted/modules/e/HEAD similarity index 100% rename from tests-clar/resources/unsymlinked.git/HEAD rename to tests/resources/typechanges/.gitted/modules/e/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/config b/tests/resources/typechanges/.gitted/modules/e/config similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/config rename to tests/resources/typechanges/.gitted/modules/e/config diff --git a/tests-clar/resources/unsymlinked.git/description b/tests/resources/typechanges/.gitted/modules/e/description similarity index 100% rename from tests-clar/resources/unsymlinked.git/description rename to tests/resources/typechanges/.gitted/modules/e/description diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/index b/tests/resources/typechanges/.gitted/modules/e/index similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/index rename to tests/resources/typechanges/.gitted/modules/e/index diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/info/exclude b/tests/resources/typechanges/.gitted/modules/e/info/exclude similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/info/exclude rename to tests/resources/typechanges/.gitted/modules/e/info/exclude diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 rename to tests/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 rename to tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 rename to tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653 rename to tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 rename to tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad rename to tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e rename to tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf rename to tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a rename to tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b rename to tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e rename to tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 rename to tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/packed-refs b/tests/resources/typechanges/.gitted/modules/e/packed-refs similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/packed-refs rename to tests/resources/typechanges/.gitted/modules/e/packed-refs diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/refs/heads/master b/tests/resources/typechanges/.gitted/modules/e/refs/heads/master similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/refs/heads/master rename to tests/resources/typechanges/.gitted/modules/e/refs/heads/master diff --git a/tests-clar/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD similarity index 100% rename from tests-clar/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD rename to tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD diff --git a/tests-clar/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8 b/tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8 rename to tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8 diff --git a/tests-clar/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff b/tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff rename to tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff diff --git a/tests-clar/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586 b/tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586 rename to tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586 diff --git a/tests-clar/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97 b/tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97 rename to tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97 diff --git a/tests-clar/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475 b/tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475 rename to tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475 diff --git a/tests-clar/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02 b/tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02 rename to tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02 diff --git a/tests-clar/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95 b/tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95 rename to tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95 diff --git a/tests-clar/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592 b/tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592 rename to tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592 diff --git a/tests-clar/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5 b/tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5 rename to tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5 diff --git a/tests-clar/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e b/tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e rename to tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e diff --git a/tests-clar/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb b/tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb rename to tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb diff --git a/tests-clar/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0 b/tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0 rename to tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0 diff --git a/tests-clar/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0 b/tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0 rename to tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0 diff --git a/tests-clar/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4 b/tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4 rename to tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4 diff --git a/tests-clar/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729 b/tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729 rename to tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729 diff --git a/tests-clar/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2 b/tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2 rename to tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2 diff --git a/tests-clar/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb b/tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb rename to tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb diff --git a/tests-clar/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad b/tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad rename to tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad diff --git a/tests-clar/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea b/tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea rename to tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea diff --git a/tests-clar/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3 b/tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3 rename to tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3 diff --git a/tests-clar/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a b/tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a rename to tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a diff --git a/tests-clar/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f b/tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f rename to tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f diff --git a/tests-clar/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7 b/tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7 rename to tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7 diff --git a/tests-clar/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a b/tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a rename to tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a diff --git a/tests-clar/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29 b/tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29 rename to tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29 diff --git a/tests-clar/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447 b/tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447 rename to tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447 diff --git a/tests-clar/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d b/tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d rename to tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d diff --git a/tests-clar/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707 b/tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707 rename to tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707 diff --git a/tests-clar/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d b/tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d rename to tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d diff --git a/tests-clar/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095 b/tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095 rename to tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095 diff --git a/tests-clar/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c b/tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c rename to tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c diff --git a/tests-clar/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad b/tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad rename to tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad diff --git a/tests-clar/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b b/tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b rename to tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b diff --git a/tests-clar/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 rename to tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e b/tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e rename to tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e diff --git a/tests-clar/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d b/tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d similarity index 100% rename from tests-clar/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d rename to tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d diff --git a/tests-clar/resources/typechanges/.gitted/refs/heads/master b/tests/resources/typechanges/.gitted/refs/heads/master similarity index 100% rename from tests-clar/resources/typechanges/.gitted/refs/heads/master rename to tests/resources/typechanges/.gitted/refs/heads/master diff --git a/tests-clar/resources/typechanges/README.md b/tests/resources/typechanges/README.md similarity index 100% rename from tests-clar/resources/typechanges/README.md rename to tests/resources/typechanges/README.md diff --git a/tests-clar/resources/typechanges/gitmodules b/tests/resources/typechanges/gitmodules similarity index 100% rename from tests-clar/resources/typechanges/gitmodules rename to tests/resources/typechanges/gitmodules diff --git a/tests/resources/unsymlinked.git/HEAD b/tests/resources/unsymlinked.git/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/unsymlinked.git/HEAD differ diff --git a/tests-clar/resources/unsymlinked.git/config b/tests/resources/unsymlinked.git/config similarity index 100% rename from tests-clar/resources/unsymlinked.git/config rename to tests/resources/unsymlinked.git/config diff --git a/tests/resources/unsymlinked.git/description b/tests/resources/unsymlinked.git/description new file mode 100644 index 000000000..498b267a8 Binary files /dev/null and b/tests/resources/unsymlinked.git/description differ diff --git a/tests-clar/resources/unsymlinked.git/info/exclude b/tests/resources/unsymlinked.git/info/exclude similarity index 100% rename from tests-clar/resources/unsymlinked.git/info/exclude rename to tests/resources/unsymlinked.git/info/exclude diff --git a/tests-clar/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf b/tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf rename to tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf diff --git a/tests-clar/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b b/tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b rename to tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b diff --git a/tests-clar/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c b/tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c rename to tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c diff --git a/tests-clar/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 b/tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 rename to tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 diff --git a/tests-clar/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c b/tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c rename to tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c diff --git a/tests-clar/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 b/tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 rename to tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 diff --git a/tests-clar/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d b/tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d rename to tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d diff --git a/tests-clar/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 b/tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 rename to tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 diff --git a/tests-clar/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 b/tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 rename to tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 diff --git a/tests-clar/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 b/tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 rename to tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 diff --git a/tests-clar/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 b/tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 rename to tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 diff --git a/tests-clar/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a b/tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a rename to tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a diff --git a/tests-clar/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 b/tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 rename to tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 diff --git a/tests-clar/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 b/tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 similarity index 100% rename from tests-clar/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 rename to tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 diff --git a/tests-clar/resources/unsymlinked.git/refs/heads/exe-file b/tests/resources/unsymlinked.git/refs/heads/exe-file similarity index 100% rename from tests-clar/resources/unsymlinked.git/refs/heads/exe-file rename to tests/resources/unsymlinked.git/refs/heads/exe-file diff --git a/tests-clar/resources/unsymlinked.git/refs/heads/master b/tests/resources/unsymlinked.git/refs/heads/master similarity index 100% rename from tests-clar/resources/unsymlinked.git/refs/heads/master rename to tests/resources/unsymlinked.git/refs/heads/master diff --git a/tests-clar/resources/unsymlinked.git/refs/heads/reg-file b/tests/resources/unsymlinked.git/refs/heads/reg-file similarity index 100% rename from tests-clar/resources/unsymlinked.git/refs/heads/reg-file rename to tests/resources/unsymlinked.git/refs/heads/reg-file diff --git a/tests-clar/revwalk/basic.c b/tests/revwalk/basic.c similarity index 81% rename from tests-clar/revwalk/basic.c rename to tests/revwalk/basic.c index e82776260..6d55aed54 100644 --- a/tests-clar/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -98,27 +98,46 @@ static int test_walk(git_revwalk *walk, const git_oid *root, return test_walk_only(walk, possible_results, results_count); } -static git_repository *_repo; -static git_revwalk *_walk; +static git_repository *_repo = NULL; +static git_revwalk *_walk = NULL; +static const char *_fixture = NULL; void test_revwalk_basic__initialize(void) { - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_revwalk_new(&_walk, _repo)); } void test_revwalk_basic__cleanup(void) { git_revwalk_free(_walk); - _walk = NULL; - git_repository_free(_repo); + + if (_fixture) + cl_git_sandbox_cleanup(); + else + git_repository_free(_repo); + + _fixture = NULL; _repo = NULL; + _walk = NULL; +} + +static void revwalk_basic_setup_walk(const char *fixture) +{ + if (fixture) { + _fixture = fixture; + _repo = cl_git_sandbox_init(fixture); + } else { + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + } + + cl_git_pass(git_revwalk_new(&_walk, _repo)); } void test_revwalk_basic__sorting_modes(void) { git_oid id; + revwalk_basic_setup_walk(NULL); + git_oid_fromstr(&id, commit_head); cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME, commit_sorting_time, 1)); @@ -132,6 +151,8 @@ void test_revwalk_basic__glob_heads(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); while (git_revwalk_next(&oid, _walk) == 0) { @@ -142,11 +163,30 @@ void test_revwalk_basic__glob_heads(void) cl_assert(i == 14); } +void test_revwalk_basic__glob_heads_with_invalid(void) +{ + int i; + git_oid oid; + + revwalk_basic_setup_walk("testrepo"); + + cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref"); + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); + + for (i = 0; !git_revwalk_next(&oid, _walk); ++i) + /* walking */; + + /* git log --branches --oneline | wc -l => 16 */ + cl_assert_equal_i(17, i); +} + void test_revwalk_basic__push_head(void) { int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); while (git_revwalk_next(&oid, _walk) == 0) { @@ -162,6 +202,8 @@ void test_revwalk_basic__push_head_hide_ref(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); @@ -178,6 +220,8 @@ void test_revwalk_basic__push_head_hide_ref_nobase(void) int i = 0; git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_revwalk_push_head(_walk)); cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed")); @@ -193,12 +237,16 @@ void test_revwalk_basic__disallow_non_commit(void) { git_oid oid; + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91")); cl_git_fail(git_revwalk_push(_walk, &oid)); } void test_revwalk_basic__push_range(void) { + revwalk_basic_setup_walk(NULL); + git_revwalk_reset(_walk); git_revwalk_sorting(_walk, 0); cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); diff --git a/tests-clar/revwalk/mergebase.c b/tests/revwalk/mergebase.c similarity index 95% rename from tests-clar/revwalk/mergebase.c rename to tests/revwalk/mergebase.c index e2617ab0e..2d01647fd 100644 --- a/tests-clar/revwalk/mergebase.c +++ b/tests/revwalk/mergebase.c @@ -123,6 +123,18 @@ void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) cl_assert_equal_sz(4, behind); } +void test_revwalk_mergebase__prefer_youngest_merge_base(void) +{ + git_oid result, one, two, expected; + + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f ")); + cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert(git_oid_cmp(&result, &expected) == 0); +} + void test_revwalk_mergebase__no_off_by_one_missing(void) { git_oid result, one, two; @@ -160,9 +172,9 @@ static void assert_mergebase_many(const char *expected_sha, int count, ...) va_end(ap); if (expected_sha == NULL) - cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, oids, count)); + cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, count, oids)); else { - cl_git_pass(git_merge_base_many(&oid, _repo, oids, count)); + cl_git_pass(git_merge_base_many(&oid, _repo, count, oids)); cl_git_pass(git_oid_fromstr(&expected, expected_sha)); cl_assert(git_oid_cmp(&expected, &oid) == 0); diff --git a/tests-clar/revwalk/signatureparsing.c b/tests/revwalk/signatureparsing.c similarity index 100% rename from tests-clar/revwalk/signatureparsing.c rename to tests/revwalk/signatureparsing.c diff --git a/tests/revwalk/simplify.c b/tests/revwalk/simplify.c new file mode 100644 index 000000000..81c19d366 --- /dev/null +++ b/tests/revwalk/simplify.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" + +void test_revwalk_simplify__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/* + * a4a7dce [0] Merge branch 'master' into br2 + |\ + | * 9fd738e [1] a fourth commit + | * 4a202b3 [2] a third commit + * | c47800c [3] branch commit one + |/ + * 5b5b025 [5] another commit + * 8496071 [4] testing +*/ +static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; + +static const char *expected_str[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ +}; + +void test_revwalk_simplify__first_parent(void) +{ + git_repository *repo; + git_revwalk *walk; + git_oid id, expected[4]; + int i, error; + + for (i = 0; i < 4; i++) { + git_oid_fromstr(&expected[i], expected_str[i]); + } + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_revwalk_new(&walk, repo)); + + git_oid_fromstr(&id, commit_head); + cl_git_pass(git_revwalk_push(walk, &id)); + git_revwalk_simplify_first_parent(walk); + + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) { + git_oid_cmp(&id, &expected[i]); + i++; + } + + cl_assert_equal_i(i, 4); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} diff --git a/tests-clar/stash/drop.c b/tests/stash/drop.c similarity index 88% rename from tests-clar/stash/drop.c rename to tests/stash/drop.c index 60b3c72e0..63ff0377c 100644 --- a/tests-clar/stash/drop.c +++ b/tests/stash/drop.c @@ -36,25 +36,27 @@ static void push_three_states(void) cl_git_mkfile("stash/zero.txt", "content\n"); cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_index_add_bypath(index, "zero.txt")); - commit_staged_files(&oid, index, signature); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); cl_assert(git_path_exists("stash/zero.txt")); + git_index_free(index); cl_git_mkfile("stash/one.txt", "content\n"); - cl_git_pass(git_stash_save(&oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); + cl_git_pass(git_stash_save( + &oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); cl_assert(!git_path_exists("stash/one.txt")); cl_assert(git_path_exists("stash/zero.txt")); cl_git_mkfile("stash/two.txt", "content\n"); - cl_git_pass(git_stash_save(&oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); + cl_git_pass(git_stash_save( + &oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); cl_assert(!git_path_exists("stash/two.txt")); cl_assert(git_path_exists("stash/zero.txt")); cl_git_mkfile("stash/three.txt", "content\n"); - cl_git_pass(git_stash_save(&oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); + cl_git_pass(git_stash_save( + &oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); cl_assert(!git_path_exists("stash/three.txt")); cl_assert(git_path_exists("stash/zero.txt")); - - git_index_free(index); } void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void) @@ -100,7 +102,7 @@ void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); - cl_git_pass(git_reflog_read(&reflog, stash)); + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); entry = git_reflog_entry_byindex(reflog, 1); git_oid_cpy(&oid, git_reflog_entry_id_old(entry)); @@ -110,7 +112,7 @@ void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) cl_git_pass(git_stash_drop(repo, 1)); - cl_git_pass(git_reflog_read(&reflog, stash)); + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); entry = git_reflog_entry_byindex(reflog, 0); cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_id_old(entry))); @@ -160,7 +162,7 @@ void test_stash_drop__dropping_the_top_stash_updates_the_stash_reference(void) retrieve_top_stash_id(&oid); cl_git_pass(git_revparse_single(&next_top_stash, repo, "stash@{1}")); - cl_assert_equal_i(false, git_oid_cmp(&oid, git_object_id(next_top_stash)) == 0); + cl_assert(git_oid_cmp(&oid, git_object_id(next_top_stash)) != 0); cl_git_pass(git_stash_drop(repo, 0)); diff --git a/tests-clar/stash/foreach.c b/tests/stash/foreach.c similarity index 100% rename from tests-clar/stash/foreach.c rename to tests/stash/foreach.c diff --git a/tests-clar/stash/save.c b/tests/stash/save.c similarity index 86% rename from tests-clar/stash/save.c rename to tests/stash/save.c index eae116ac5..3d92b26bd 100644 --- a/tests-clar/stash/save.c +++ b/tests/stash/save.c @@ -113,33 +113,15 @@ $ git status --short cl_assert_equal_i(GIT_STATUS_WT_NEW, status); } -static void assert_status( - const char *path, - int status_flags) -{ - unsigned int status; - int error; - - error = git_status_file(&status, repo, path); - - if (status_flags < 0) { - cl_assert_equal_i(status_flags, error); - return; - } - - cl_assert_equal_i(0, error); - cl_assert_equal_i((unsigned int)status_flags, status); -} - void test_stash_save__can_keep_index(void) { cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX)); - assert_status("what", GIT_STATUS_INDEX_MODIFIED); - assert_status("how", GIT_STATUS_INDEX_MODIFIED); - assert_status("who", GIT_STATUS_CURRENT); - assert_status("when", GIT_STATUS_WT_NEW); - assert_status("just.ignore", GIT_STATUS_IGNORED); + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); } static void assert_commit_message_contains(const char *revision, const char *fragment) @@ -194,7 +176,7 @@ void test_stash_save__cannot_stash_against_an_unborn_branch(void) cl_git_pass(git_reference_symbolic_create(&head, repo, "HEAD", "refs/heads/unborn", 1)); - cl_assert_equal_i(GIT_EORPHANEDHEAD, + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); git_reference_free(head); @@ -241,7 +223,7 @@ void test_stash_save__stashing_updates_the_reflog(void) void test_stash_save__cannot_stash_when_there_are_no_local_change(void) { git_index *index; - git_oid commit_oid, stash_tip_oid; + git_oid stash_tip_oid; cl_git_pass(git_repository_index(&index, repo)); @@ -251,8 +233,7 @@ void test_stash_save__cannot_stash_when_there_are_no_local_change(void) */ cl_git_pass(git_index_add_bypath(index, "what")); cl_git_pass(git_index_add_bypath(index, "who")); - cl_git_pass(git_index_write(index)); - commit_staged_files(&commit_oid, index, signature); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); git_index_free(index); cl_assert_equal_i(GIT_ENOTFOUND, @@ -309,25 +290,25 @@ void test_stash_save__can_stage_normal_then_stage_untracked(void) * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when */ - assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); - assert_status("how", GIT_STATUS_INDEX_MODIFIED); - assert_status("who", GIT_STATUS_WT_MODIFIED); - assert_status("when", GIT_STATUS_WT_NEW); - assert_status("just.ignore", GIT_STATUS_IGNORED); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - assert_status("what", GIT_STATUS_CURRENT); - assert_status("how", GIT_STATUS_CURRENT); - assert_status("who", GIT_STATUS_CURRENT); - assert_status("when", GIT_STATUS_WT_NEW); - assert_status("just.ignore", GIT_STATUS_IGNORED); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - assert_status("what", GIT_STATUS_CURRENT); - assert_status("how", GIT_STATUS_CURRENT); - assert_status("who", GIT_STATUS_CURRENT); - assert_status("when", GIT_ENOTFOUND); - assert_status("just.ignore", GIT_STATUS_IGNORED); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ @@ -361,11 +342,11 @@ void test_stash_save__including_untracked_without_any_untracked_file_creates_an_ { cl_git_pass(p_unlink("stash/when")); - assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); - assert_status("how", GIT_STATUS_INDEX_MODIFIED); - assert_status("who", GIT_STATUS_WT_MODIFIED); - assert_status("when", GIT_ENOTFOUND); - assert_status("just.ignore", GIT_STATUS_IGNORED); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); diff --git a/tests-clar/stash/stash_helpers.c b/tests/stash/stash_helpers.c similarity index 72% rename from tests-clar/stash/stash_helpers.c rename to tests/stash/stash_helpers.c index f462a1351..8b7d685f8 100644 --- a/tests-clar/stash/stash_helpers.c +++ b/tests/stash/stash_helpers.c @@ -2,38 +2,8 @@ #include "fileops.h" #include "stash_helpers.h" -void commit_staged_files( - git_oid *commit_oid, - git_index *index, - git_signature *signature) -{ - git_tree *tree; - git_oid tree_oid; - git_repository *repo; - - repo = git_index_owner(index); - - cl_git_pass(git_index_write_tree(&tree_oid, index)); - - cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - - cl_git_pass(git_commit_create_v( - commit_oid, - repo, - "HEAD", - signature, - signature, - NULL, - "Initial commit", - tree, - 0)); - - git_tree_free(tree); -} - void setup_stash(git_repository *repo, git_signature *signature) { - git_oid commit_oid; git_index *index; cl_git_pass(git_repository_index(&index, repo)); @@ -50,9 +20,8 @@ void setup_stash(git_repository *repo, git_signature *signature) cl_git_pass(git_index_add_bypath(index, "how")); cl_git_pass(git_index_add_bypath(index, "who")); cl_git_pass(git_index_add_bypath(index, ".gitignore")); - cl_git_pass(git_index_write(index)); - commit_staged_files(&commit_oid, index, signature); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ @@ -66,3 +35,22 @@ void setup_stash(git_repository *repo, git_signature *signature) git_index_free(index); } + +void assert_status( + git_repository *repo, + const char *path, + int status_flags) +{ + unsigned int status; + int error; + + error = git_status_file(&status, repo, path); + + if (status_flags < 0) { + cl_assert_equal_i(status_flags, error); + return; + } + + cl_assert_equal_i(0, error); + cl_assert_equal_i((unsigned int)status_flags, status); +} diff --git a/tests/stash/stash_helpers.h b/tests/stash/stash_helpers.h new file mode 100644 index 000000000..66d758fe2 --- /dev/null +++ b/tests/stash/stash_helpers.h @@ -0,0 +1,8 @@ +void setup_stash( + git_repository *repo, + git_signature *signature); + +void assert_status( + git_repository *repo, + const char *path, + int status_flags); diff --git a/tests/stash/submodules.c b/tests/stash/submodules.c new file mode 100644 index 000000000..137c4408c --- /dev/null +++ b/tests/stash/submodules.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "stash_helpers.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; + +static git_submodule *sm; + +void test_stash_submodules__initialize(void) +{ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + repo = setup_fixture_submodules(); + + cl_git_pass(git_submodule_lookup(&sm, repo, "testrepo")); +} + +void test_stash_submodules__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; +} + +void test_stash_submodules__does_not_stash_modified_submodules(void) +{ + static git_index *smindex; + static git_repository *smrepo; + + assert_status(repo, "modified", GIT_STATUS_WT_MODIFIED); + + /* modify file in submodule */ + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "README")); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "modified", GIT_STATUS_CURRENT); + + git_index_free(smindex); + git_repository_free(smrepo); +} + +void test_stash_submodules__stash_is_empty_with_modified_submodules(void) +{ + static git_index *smindex; + static git_repository *smrepo; + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + assert_status(repo, "modified", GIT_STATUS_CURRENT); + + /* modify file in submodule */ + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "README")); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + cl_git_fail_with(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT), GIT_ENOTFOUND); + + git_index_free(smindex); + git_repository_free(smrepo); +} diff --git a/tests-clar/status/ignore.c b/tests/status/ignore.c similarity index 80% rename from tests-clar/status/ignore.c rename to tests/status/ignore.c index 4f6879cfc..acdc8fb58 100644 --- a/tests-clar/status/ignore.c +++ b/tests/status/ignore.c @@ -459,3 +459,124 @@ void test_status_ignore__automatically_ignore_bad_files(void) cl_git_pass(git_status_should_ignore(&ignored, g_repo, "path/whatever.c")); cl_assert(!ignored); } + +void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) +{ + status_entry_single st; + char *test_cases[] = { + "!file", + "#blah", + "[blah]", + "[attr]", + "[attr]blah", + NULL + }; + int i; + + for (i = 0; *(test_cases + i) != NULL; i++) { + git_buf file = GIT_BUF_INIT; + char *file_name = *(test_cases + i); + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_buf_joinpath(&file, "empty_standard_repo", file_name)); + cl_git_mkfile(git_buf_cstr(&file), "Please don't ignore me!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert(st.count == 1); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, repo, file_name)); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_sandbox_cleanup(); + git_buf_free(&file); + } +} + +void test_status_ignore__issue_1766_negated_ignores(void) +{ + int ignored = 0; + unsigned int status; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/a", NULL, 0775)); + cl_git_mkfile( + "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/a/ignoreme", "I should be ignored\n"); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/.gitignore")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/ignoreme")); + cl_assert(ignored); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/b", NULL, 0775)); + cl_git_mkfile( + "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/b/ignoreme", "I should be ignored\n"); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "b/.gitignore")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "b/ignoreme")); + cl_assert(ignored); + + /* shouldn't have changed results from first couple either */ + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/.gitignore")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "a/ignoreme")); + cl_assert(ignored); + + /* status should find the two ignore files and nothing else */ + + cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths[] = { + "a/.gitignore", + "a/ignoreme", + "b/.gitignore", + "b/ignoreme", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = paths; + counts.expected_statuses = statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } +} + diff --git a/tests-clar/status/renames.c b/tests/status/renames.c similarity index 67% rename from tests-clar/status/renames.c rename to tests/status/renames.c index 80ff26020..16fd02676 100644 --- a/tests-clar/status/renames.c +++ b/tests/status/renames.c @@ -11,6 +11,8 @@ static git_repository *g_repo = NULL; void test_status_renames__initialize(void) { g_repo = cl_git_sandbox_init("renames"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); } void test_status_renames__cleanup(void) @@ -67,14 +69,14 @@ static void test_status( actual = git_status_byindex(status_list, i); expected = &expected_list[i]; - cl_assert_equal_i((int)expected->status, (int)actual->status); - oldname = actual->head_to_index ? actual->head_to_index->old_file.path : actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path : actual->head_to_index ? actual->head_to_index->new_file.path : NULL; + cl_assert_equal_i_fmt(expected->status, actual->status, "%04x"); + if (oldname) cl_assert(git__strcmp(oldname, expected->oldname) == 0); else @@ -153,6 +155,65 @@ void test_status_renames__head2index_two(void) git_index_free(index); } +void test_status_renames__head2index_no_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__index2workdir_one(void) { git_status_list *statuslist; @@ -197,6 +258,32 @@ void test_status_renames__index2workdir_two(void) git_status_list_free(statuslist); } +void test_status_renames__index2workdir_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__both_one(void) { git_index *index; @@ -274,6 +361,91 @@ void test_status_renames__both_two(void) git_index_free(index); } + +void test_status_renames__both_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "songof7cities.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "songof7cities.txt", "sixserving.txt"); + rename_file(g_repo, "_temp_.txt", "songof7cities.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "songof7cities.txt", "_temp_.txt"); + rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 3); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__rewrites_only_for_renames(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + void test_status_renames__both_casechange_one(void) { git_index *index; @@ -335,14 +507,14 @@ void test_status_renames__both_casechange_two(void) "untimely.txt", "untimeliest.txt" } }; struct status_entry expected_case[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | - GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "ikeepsix.txt", "ikeepsix.txt" }, - { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, - "sixserving.txt", "SixServing.txt" }, { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED, "songof7cities.txt", "SONGOF7.txt" }, + { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "SixServing.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix.txt" }, { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "untimely.txt", "untimeliest.txt" } }; diff --git a/tests-clar/status/single.c b/tests/status/single.c similarity index 100% rename from tests-clar/status/single.c rename to tests/status/single.c diff --git a/tests-clar/status/status_data.h b/tests/status/status_data.h similarity index 68% rename from tests-clar/status/status_data.h rename to tests/status/status_data.h index a41bde7c2..8ad4235fd 100644 --- a/tests-clar/status/status_data.h +++ b/tests/status/status_data.h @@ -1,5 +1,9 @@ #include "status_helpers.h" +// A utf-8 string with 83 characters, but 249 bytes. +static const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + + /* entries for a plain copy of tests/resources/status */ static const char *entry_paths0[] = { @@ -250,3 +254,73 @@ static const unsigned int entry_statuses4[] = { }; static const int entry_count4 = 23; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the HEAD and the index (changes to be committed) + */ + +static const char *entry_paths5[] = { + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", +}; + +static const unsigned int entry_statuses5[] = { + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, +}; + +static const int entry_count5 = 8; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the workdir and the index (changes not staged, untracked files) + */ + +static const char *entry_paths6[] = { + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses6[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count6 = 13; diff --git a/tests-clar/status/status_helpers.c b/tests/status/status_helpers.c similarity index 100% rename from tests-clar/status/status_helpers.c rename to tests/status/status_helpers.c diff --git a/tests-clar/status/status_helpers.h b/tests/status/status_helpers.h similarity index 100% rename from tests-clar/status/status_helpers.h rename to tests/status/status_helpers.h diff --git a/tests-clar/status/submodules.c b/tests/status/submodules.c similarity index 95% rename from tests-clar/status/submodules.c rename to tests/status/submodules.c index af8707721..ef2888f7d 100644 --- a/tests-clar/status/submodules.c +++ b/tests/status/submodules.c @@ -9,25 +9,18 @@ static git_repository *g_repo = NULL; void test_status_submodules__initialize(void) { - g_repo = cl_git_sandbox_init("submodules"); - - cl_fixture_sandbox("testrepo.git"); - - rewrite_gitmodules(git_repository_workdir(g_repo)); - - p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); } void test_status_submodules__cleanup(void) { - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("testrepo.git"); } void test_status_submodules__api(void) { git_submodule *sm; + g_repo = setup_fixture_submodules(); + cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND); cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND); @@ -42,6 +35,8 @@ void test_status_submodules__0(void) { int counts = 0; + g_repo = setup_fixture_submodules(); + cl_assert(git_path_isdir("submodules/.git")); cl_assert(git_path_isdir("submodules/testrepo/.git")); cl_assert(git_path_isfile("submodules/.gitmodules")); @@ -86,6 +81,8 @@ void test_status_submodules__1(void) { status_entry_counts counts; + g_repo = setup_fixture_submodules(); + cl_assert(git_path_isdir("submodules/.git")); cl_assert(git_path_isdir("submodules/testrepo/.git")); cl_assert(git_path_isfile("submodules/.gitmodules")); @@ -104,6 +101,7 @@ void test_status_submodules__1(void) void test_status_submodules__single_file(void) { unsigned int status = 0; + g_repo = setup_fixture_submodules(); cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); cl_assert(!status); } @@ -134,6 +132,8 @@ void test_status_submodules__moved_head(void) GIT_STATUS_WT_NEW }; + g_repo = setup_fixture_submodules(); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); cl_git_pass(git_submodule_open(&smrepo, sm)); @@ -192,6 +192,8 @@ void test_status_submodules__dirty_workdir_only(void) GIT_STATUS_WT_NEW }; + g_repo = setup_fixture_submodules(); + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); diff --git a/tests-clar/status/worktree.c b/tests/status/worktree.c similarity index 90% rename from tests-clar/status/worktree.c rename to tests/status/worktree.c index 920671e13..34be6d34c 100644 --- a/tests-clar/status/worktree.c +++ b/tests/status/worktree.c @@ -40,6 +40,48 @@ void test_status_worktree__whole_repository(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } +void assert_show(const int entry_counts, const char *entry_paths[], + const unsigned int entry_statuses[], git_status_show_t show) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_counts; + counts.expected_paths = entry_paths; + counts.expected_statuses = entry_statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + opts.show = show; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__show_index_and_workdir(void) +{ + assert_show(entry_count0, entry_paths0, entry_statuses0, + GIT_STATUS_SHOW_INDEX_AND_WORKDIR); +} + +void test_status_worktree__show_index_only(void) +{ + assert_show(entry_count5, entry_paths5, entry_statuses5, + GIT_STATUS_SHOW_INDEX_ONLY); +} + +void test_status_worktree__show_workdir_only(void) +{ + assert_show(entry_count6, entry_paths6, entry_statuses6, + GIT_STATUS_SHOW_WORKDIR_ONLY); +} + /* this test is equivalent to t18-status.c:statuscb1 */ void test_status_worktree__empty_repository(void) { @@ -77,7 +119,7 @@ void test_status_worktree__purged_worktree(void) /* first purge the contents of the worktree */ cl_git_pass(git_buf_sets(&workdir, git_repository_workdir(repo))); - cl_git_pass(git_path_direach(&workdir, remove_file_cb, NULL)); + cl_git_pass(git_path_direach(&workdir, 0, remove_file_cb, NULL)); git_buf_free(&workdir); /* now get status */ @@ -428,16 +470,15 @@ void test_status_worktree__conflict_with_diff3(void) cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "modified_file", 0)); - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, - &our_entry, &their_entry)); + cl_git_pass(git_index_conflict_add( + index, &ancestor_entry, &our_entry, &their_entry)); + cl_git_pass(git_index_write(index)); + git_index_free(index); cl_git_pass(git_status_file(&status, repo, "modified_file")); cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status); - - git_index_free(index); } static const char *filemode_paths[] = { @@ -590,35 +631,12 @@ void test_status_worktree__conflicted_item(void) static void stage_and_commit(git_repository *repo, const char *path) { - git_oid tree_oid, commit_oid; - git_tree *tree; - git_signature *signature; git_index *index; cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_index_add_bypath(index, path)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_write_tree(&tree_oid, index)); + cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n"); git_index_free(index); - - cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); - - cl_git_pass(git_commit_create_v( - &commit_oid, - repo, - "HEAD", - signature, - signature, - NULL, - "Initial commit\n\0", - tree, - 0)); - - git_tree_free(tree); - git_signature_free(signature); } static void assert_ignore_case( @@ -823,3 +841,35 @@ void test_status_worktree__sorting_by_case(void) cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); } + +void test_status_worktree__long_filenames(void) +{ + char path[260*4+1]; + const char *expected_paths[] = {path}; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + // Create directory with amazingly long filename + sprintf(path, "empty_standard_repo/%s", longname); + cl_git_pass(git_futils_mkdir_r(path, NULL, 0777)); + sprintf(path, "empty_standard_repo/%s/foo", longname); + cl_git_mkfile(path, "dummy"); + + sprintf(path, "%s/foo", longname); + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + diff --git a/tests-clar/status/worktree_init.c b/tests/status/worktree_init.c similarity index 100% rename from tests-clar/status/worktree_init.c rename to tests/status/worktree_init.c diff --git a/tests/stress/diff.c b/tests/stress/diff.c new file mode 100644 index 000000000..3d2092614 --- /dev/null +++ b/tests/stress/diff.c @@ -0,0 +1,146 @@ +#include "clar_libgit2.h" +#include "../diff/diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_stress_diff__initialize(void) +{ +} + +void test_stress_diff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define ANOTHER_POEM \ +"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" + +static void test_with_many(int expected_new) +{ + git_index *index; + git_tree *tree, *new_tree; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_free(diff); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "yoyoyo"); + cl_git_pass(git_revparse_single( + (git_object **)&new_tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree, new_tree, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_free(diff); + + git_tree_free(new_tree); + git_tree_free(tree); + git_index_free(index); +} + +void test_stress_diff__rename_big_files(void) +{ + git_index *index; + char tmp[64]; + int i, j; + git_buf b = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + for (j = i * 256; j > 0; --j) + git_buf_printf(&b, "more content %d\n", i); + cl_git_mkfile(tmp, b.ptr); + } + + for (i = 0; i < 100; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_buf_free(&b); + git_index_free(index); + + test_with_many(100); +} + +void test_stress_diff__rename_many_files(void) +{ + git_index *index; + char tmp[64]; + int i; + git_buf b = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + snprintf(b.ptr, 9, "%08d", i); + b.ptr[8] = '\n'; + cl_git_mkfile(tmp, b.ptr); + } + git_buf_free(&b); + + for (i = 0; i < 2500; i += 1) { + snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_index_free(index); + + test_with_many(2500); +} diff --git a/tests-clar/submodule/lookup.c b/tests/submodule/lookup.c similarity index 58% rename from tests-clar/submodule/lookup.c rename to tests/submodule/lookup.c index acf8f6462..5f320e702 100644 --- a/tests-clar/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -1,25 +1,13 @@ #include "clar_libgit2.h" #include "submodule_helpers.h" #include "posix.h" +#include "git2/sys/repository.h" static git_repository *g_repo = NULL; void test_submodule_lookup__initialize(void) { - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - /* must create submod2_target before rewrite so prettify will work */ - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); -} - -void test_submodule_lookup__cleanup(void) -{ - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("submod2_target"); + g_repo = setup_fixture_submod2(); } void test_submodule_lookup__simple_lookup(void) @@ -112,3 +100,73 @@ void test_submodule_lookup__foreach(void) cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); cl_assert_equal_i(8, data.count); } + +void test_submodule_lookup__lookup_even_with_unborn_head(void) +{ + git_reference *head; + git_submodule *sm; + + /* put us on an unborn branch */ + cl_git_pass(git_reference_symbolic_create( + &head, g_repo, "HEAD", "refs/heads/garbage", 1)); + git_reference_free(head); + + /* lookup existing */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is not in HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + + /* lookup git repo subdir that is not added as submodule */ + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, g_repo, "not-submodule")); + + /* lookup existing directory that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_dir")); + + /* lookup existing file that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_file")); + + /* lookup non-existent item */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "no_such_file")); +} + +void test_submodule_lookup__lookup_even_with_missing_index(void) +{ + git_index *idx; + git_submodule *sm; + + /* give the repo an empty index */ + cl_git_pass(git_index_new(&idx)); + git_repository_set_index(g_repo, idx); + git_index_free(idx); + + /* lookup existing */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is not in HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + + /* lookup git repo subdir that is not added as submodule */ + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, g_repo, "not-submodule")); + + /* lookup existing directory that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_dir")); + + /* lookup existing file that is not a submodule */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "just_a_file")); + + /* lookup non-existent item */ + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "no_such_file")); +} diff --git a/tests-clar/submodule/modify.c b/tests/submodule/modify.c similarity index 91% rename from tests-clar/submodule/modify.c rename to tests/submodule/modify.c index 94eb3738a..e326287a6 100644 --- a/tests-clar/submodule/modify.c +++ b/tests/submodule/modify.c @@ -11,20 +11,7 @@ static git_repository *g_repo = NULL; void test_submodule_modify__initialize(void) { - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - /* must create submod2_target before rewrite so prettify will work */ - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); -} - -void test_submodule_modify__cleanup(void) -{ - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("submod2_target"); + g_repo = setup_fixture_submod2(); } void test_submodule_modify__add(void) @@ -204,10 +191,10 @@ void test_submodule_modify__edit_and_save(void) cl_git_pass(git_submodule_set_url(sm1, old_url)); cl_assert_equal_i( (int)GIT_SUBMODULE_IGNORE_UNTRACKED, - (int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT)); + (int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, - (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT)); + (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET)); cl_assert_equal_i( 1, git_submodule_set_fetch_recurse_submodules(sm1, old_fetchrecurse)); @@ -228,10 +215,10 @@ void test_submodule_modify__edit_and_save(void) cl_git_pass(git_submodule_save(sm1)); /* attempt to "revert" values */ - git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT); - git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT); + git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET); + git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET); - /* but ignore and update should NOT revert because the DEFAULT + /* but ignore and update should NOT revert because the RESET * should now be the newly saved value... */ cl_assert_equal_i( diff --git a/tests-clar/submodule/status.c b/tests/submodule/status.c similarity index 92% rename from tests-clar/submodule/status.c rename to tests/submodule/status.c index 68110bdd5..f5111c84f 100644 --- a/tests-clar/submodule/status.c +++ b/tests/submodule/status.c @@ -9,21 +9,11 @@ static git_repository *g_repo = NULL; void test_submodule_status__initialize(void) { - g_repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - /* must create submod2_target before rewrite so prettify will work */ - rewrite_gitmodules(git_repository_workdir(g_repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); + g_repo = setup_fixture_submod2(); } void test_submodule_status__cleanup(void) { - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("submod2_target"); } void test_submodule_status__unchanged(void) @@ -326,6 +316,7 @@ void test_submodule_status__ignore_all(void) typedef struct { size_t counter; const char **paths; + int *statuses; } submodule_expectations; static int confirm_submodule_status( @@ -336,6 +327,7 @@ static int confirm_submodule_status( while (git__suffixcmp(exp->paths[exp->counter], "/") == 0) exp->counter++; + cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags); cl_assert_equal_s(exp->paths[exp->counter++], path); GIT_UNUSED(status_flags); @@ -365,7 +357,24 @@ void test_submodule_status__iterator(void) "sm_unchanged", NULL }; - submodule_expectations exp = { 0, expected }; + static int expected_flags[] = { + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */ + 0, /* "just_a_dir/" will be skipped */ + GIT_STATUS_CURRENT, /* "just_a_dir/contents" */ + GIT_STATUS_CURRENT, /* "just_a_file" */ + GIT_STATUS_IGNORED, /* "not" (contains .git) */ + GIT_STATUS_IGNORED, /* "not-submodule" (contains .git) */ + GIT_STATUS_CURRENT, /* "README.txt */ + GIT_STATUS_INDEX_NEW, /* "sm_added_and_uncommited" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */ + GIT_STATUS_CURRENT, /* "sm_unchanged" */ + 0 + }; + submodule_expectations exp = { 0, expected, expected_flags }; git_status_options opts = GIT_STATUS_OPTIONS_INIT; cl_git_pass(git_iterator_for_workdir(&iter, g_repo, @@ -378,7 +387,9 @@ void test_submodule_status__iterator(void) opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; cl_git_pass(git_status_foreach_ext( g_repo, &opts, confirm_submodule_status, &exp)); diff --git a/tests-clar/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c similarity index 65% rename from tests-clar/submodule/submodule_helpers.c rename to tests/submodule/submodule_helpers.c index 0c3e79f71..d5750675c 100644 --- a/tests-clar/submodule/submodule_helpers.c +++ b/tests/submodule/submodule_helpers.c @@ -4,6 +4,7 @@ #include "util.h" #include "posix.h" #include "submodule_helpers.h" +#include "git2/sys/repository.h" /* rewrite gitmodules -> .gitmodules * rewrite the empty or relative urls inside each module @@ -82,3 +83,45 @@ void rewrite_gitmodules(const char *workdir) git_buf_free(&out_f); git_buf_free(&path); } + +static void cleanup_fixture_submodules(void *payload) +{ + cl_git_sandbox_cleanup(); /* either "submodules" or "submod2" */ + + if (payload) + cl_fixture_cleanup(payload); +} + +git_repository *setup_fixture_submodules(void) +{ + git_repository *repo = cl_git_sandbox_init("submodules"); + + cl_fixture_sandbox("testrepo.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_submod2(void) +{ + git_repository *repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); + p_rename("submod2/not/.gitted", "submod2/not/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} diff --git a/tests/submodule/submodule_helpers.h b/tests/submodule/submodule_helpers.h new file mode 100644 index 000000000..610c40720 --- /dev/null +++ b/tests/submodule/submodule_helpers.h @@ -0,0 +1,5 @@ +extern void rewrite_gitmodules(const char *workdir); + +/* these will automatically set a cleanup callback */ +extern git_repository *setup_fixture_submodules(void); +extern git_repository *setup_fixture_submod2(void); diff --git a/tests-clar/threads/basic.c b/tests/threads/basic.c similarity index 50% rename from tests-clar/threads/basic.c rename to tests/threads/basic.c index a15c53140..a329ee7f9 100644 --- a/tests-clar/threads/basic.c +++ b/tests/threads/basic.c @@ -21,3 +21,16 @@ void test_threads_basic__cache(void) // run several threads polling the cache at the same time cl_assert(1 == 1); } + +void test_threads_basic__multiple_init(void) +{ + git_repository *nested_repo; + + git_threads_init(); + cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); + git_repository_free(nested_repo); + + git_threads_shutdown(); + cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); + git_repository_free(nested_repo); +} diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c new file mode 100644 index 000000000..3c651e341 --- /dev/null +++ b/tests/threads/refdb.c @@ -0,0 +1,213 @@ +#include "clar_libgit2.h" +#include "git2/refdb.h" +#include "refdb.h" + +static git_repository *g_repo; +static int g_expected = 0; + +void test_threads_refdb__initialize(void) +{ + g_repo = NULL; +} + +void test_threads_refdb__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +#define REPEAT 20 +#define THREADS 20 + +static void *iterate_refs(void *arg) +{ + git_reference_iterator *i; + git_reference *ref; + int count = 0; + + cl_git_pass(git_reference_iterator_new(&i, g_repo)); + + for (count = 0; !git_reference_next(&ref, i); ++count) { + cl_assert(ref != NULL); + git_reference_free(ref); + } + + if (g_expected > 0) + cl_assert_equal_i(g_expected, count); + + git_reference_iterator_free(i); + + return arg; +} + +void test_threads_refdb__iterator(void) +{ + int r, t; + git_thread th[THREADS]; + int id[THREADS]; + git_oid head; + git_reference *ref; + char name[128]; + git_refdb *refdb; + + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (r = 0; r < 200; ++r) { + snprintf(name, sizeof(name), "refs/heads/direct-%03d", r); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + g_expected = 206; + + for (r = 0; r < REPEAT; ++r) { + g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ + + for (t = 0; t < THREADS; ++t) { + id[t] = t; +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t])); +#else + th[t] = t; + iterate_refs(&id[t]); +#endif + } + +#ifdef GIT_THREADS + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } +#endif + + memset(th, 0, sizeof(th)); + } +} + +static void *create_refs(void *arg) +{ + int *id = arg, i; + git_oid head; + char name[128]; + git_reference *ref[10]; + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + for (i = 0; i < 10; ++i) { + snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", *id, i); + cl_git_pass(git_reference_create(&ref[i], g_repo, name, &head, 0)); + + if (i == 5) { + git_refdb *refdb; + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + } + } + + for (i = 0; i < 10; ++i) + git_reference_free(ref[i]); + + return arg; +} + +static void *delete_refs(void *arg) +{ + int *id = arg, i; + git_reference *ref; + char name[128]; + + for (i = 0; i < 10; ++i) { + snprintf( + name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i); + + if (!git_reference_lookup(&ref, g_repo, name)) { + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + } + + if (i == 5) { + git_refdb *refdb; + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + } + } + + return arg; +} + +void test_threads_refdb__edit_while_iterate(void) +{ + int r, t; + int id[THREADS]; + git_oid head; + git_reference *ref; + char name[128]; + git_refdb *refdb; + +#ifdef GIT_THREADS + git_thread th[THREADS]; +#endif + + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (r = 0; r < 50; ++r) { + snprintf(name, sizeof(name), "refs/heads/starter-%03d", r); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0)); + git_reference_free(ref); + } + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + g_expected = -1; + + g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ + + for (t = 0; t < THREADS; ++t) { + void *(*fn)(void *arg); + + switch (t & 0x3) { + case 0: fn = create_refs; break; + case 1: fn = delete_refs; break; + default: fn = iterate_refs; break; + } + + id[t] = t; +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&th[t], NULL, fn, &id[t])); +#else + fn(&id[t]); +#endif + } + +#ifdef GIT_THREADS + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } + + memset(th, 0, sizeof(th)); + + for (t = 0; t < THREADS; ++t) { + id[t] = t; + cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t])); + } + + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(th[t], NULL)); + } +#endif +} diff --git a/tests-clar/trace/trace.c b/tests/trace/trace.c similarity index 100% rename from tests-clar/trace/trace.c rename to tests/trace/trace.c diff --git a/tests-clar/valgrind-supp-mac.txt b/tests/valgrind-supp-mac.txt similarity index 87% rename from tests-clar/valgrind-supp-mac.txt rename to tests/valgrind-supp-mac.txt index fcc7ede86..99833d091 100644 --- a/tests-clar/valgrind-supp-mac.txt +++ b/tests/valgrind-supp-mac.txt @@ -154,3 +154,31 @@ fun:printf fun:clar_print_init } +{ + molo-1 + Memcheck:Leak + fun:malloc_zone_malloc + ... + fun:_objc_init +} +{ + molo-2 + Memcheck:Leak + fun:malloc_zone_calloc + ... + fun:_objc_init +} +{ + molo-3 + Memcheck:Leak + fun:malloc + ... + fun:_objc_init +} +{ + molo-4 + Memcheck:Leak + fun:malloc + ... + fun:dyld_register_image_state_change_handler +}