diff --git a/.gitignore b/.gitignore index 254e63db7..45d7b1957 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +/tests-clar/clar.h +/tests-clar/clar_main.c +/tests-clar/clar_main.c.rule /apidocs /trash-*.exe /libgit2.pc @@ -19,6 +22,8 @@ msvc/Release/ *.vc*proj* *.sdf *.opensdf +*.aps CMake* *.cmake .DS_Store +*~ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..2713651a8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +# Travis-CI Build for libgit2 +# see travis-ci.org for details + +# As CMake is not officially supported we use erlang VMs +language: erlang + +# Settings to try +env: + - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" + - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" + - CC=i586-mingw32msvc-gcc OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" + +# Make sure CMake is installed +install: + - sudo apt-get install cmake + +# Run the Build script +script: + - mkdir _build + - cd _build + - cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS + - cmake --build . --target install + +# Run Tests +after_script: + - ctest -V . + +# Only watch the development branch +branches: + only: + - development + +# Notify development list when needed +notifications: + recipients: + - vicent@github.com + email: + on_success: change + on_failure: always diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..03904ff55 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,64 @@ +The following people contribute or have contributed +to the libgit2 project (sorted alphabetically): + +Alex Budovski +Alexei Sholik +Andreas Ericsson +Ankur Sethi +Ben Noordhuis +Benjamin C Meyer +Brian Lopez +Carlos Martín Nieto +Colin Timmermans +Daniel Huckstep +Dave Borowitz +David Boyce +David Glesser +Dmitry Kovega +Emeric Fermas +Emmanuel Rodriguez +Ingmar Vanhassel +J. David Ibáñez +Jakob Pfender +Jason Penny +Jason R. McNeil +Jerome Lambourg +Johan 't Hart +John Wiegley +Jonathan "Duke" Leto +Julien Miotte +Julio Espinoza-Sokal +Justin Love +Kirill A. Shutemov +Lambert CLARA +Luc Bertrand +Marc Pegon +Marcel Groothuis +Marco Villegas +Olivier Ramonat +Peter Drahoš +Pierre Habouzit +Przemyslaw Pawelczyk +Ramsay Jones +Robert G. Jakabosky +Romain Geissler +Romain Muller +Russell Belfer +Sakari Jokinen +Sam +Sarath Lakshman +Sascha Peilicke +Scott Chacon +Sebastian Schuberth +Sergey Nikishin +Shawn O. Pearce +Shuhei Tanuma +Steve Frécinaux +Tim Branyen +Tim Clem +Tim Harder +Trent Mick +Vicent Marti +antong +kelly.leahy +schu diff --git a/CMakeLists.txt b/CMakeLists.txt index 93747bb64..9bff1ba0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # > mkdir build && cd build # > cmake .. [-DSETTINGS=VALUE] # > cmake --build . -# +# # Testing: # > ctest -V # @@ -16,7 +16,7 @@ SET(CMAKE_SYSTEM_NAME "Generic") PROJECT(libgit2 C) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -FILE(STRINGS "include/git2.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$") +FILE(STRINGS "include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$") STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${GIT2_HEADER}") STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_MINOR "${GIT2_HEADER}") @@ -24,37 +24,26 @@ STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}") # Find required dependencies -INCLUDE_DIRECTORIES(deps/zlib src include) +INCLUDE_DIRECTORIES(src include deps/http-parser) -# Try finding openssl -#FIND_PACKAGE(OpenSSL) -IF (OPENSSL_CRYPTO_LIBRARIES) - SET(SHA1_TYPE "openssl" CACHE STRING "Which SHA1 implementation to use: builtin, ppc, openssl") -ELSEIF () - SET(SHA1_TYPE "ppc" CACHE STRING "Which SHA1 implementation to use: builtin, ppc") -ENDIF () +FILE(GLOB SRC_HTTP deps/http-parser/*.c) -INCLUDE(FindPkgConfig) +IF (NOT WIN32) + FIND_PACKAGE(ZLIB) +ELSE() + # Windows doesn't understand POSIX regex on its own + INCLUDE_DIRECTORIES(deps/regex) + SET(SRC_REGEX deps/regex/regex.c) +ENDIF() -# Show SQLite3 settings in GUI (if they won't be found out) -SET(SQLITE3_INCLUDE_DIRS "" CACHE PATH "SQLite include directory") -SET(SQLITE3_LIBRARIES "" CACHE FILEPATH "SQLite library") - -# Are SQLite3 variables already set up? (poor Windows/no pkg-config/no sqlite3.pc) -IF (SQLITE3_INCLUDE_DIRS AND SQLITE3_LIBRARIES) - SET(SQLITE3_FOUND 1) -ENDIF () - -# Try to find SQLite3 via pkg-config -IF (PKG_CONFIG_FOUND AND NOT SQLITE3_FOUND) - pkg_check_modules(SQLITE3 sqlite3) -ENDIF () - -# Compile SQLite backend if SQLite3 is available -IF (SQLITE3_FOUND) - ADD_DEFINITIONS(-DGIT2_SQLITE_BACKEND) - INCLUDE_DIRECTORIES(${SQLITE3_INCLUDE_DIRS}) -ENDIF () +IF (ZLIB_FOUND) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) + LINK_LIBRARIES(${ZLIB_LIBRARIES}) +ELSE (ZLIB_FOUND) + INCLUDE_DIRECTORIES(deps/zlib) + ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP) + FILE(GLOB SRC_ZLIB deps/zlib/*.c) +ENDIF() # Installation paths SET(INSTALL_BIN bin CACHE PATH "Where to install binaries to.") @@ -63,14 +52,52 @@ SET(INSTALL_INC include CACHE PATH "Where to install headers to.") # Build options OPTION (BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON) -OPTION (BUILD_TESTS "Build Tests" ON) OPTION (THREADSAFE "Build libgit2 as threadsafe" OFF) +OPTION (BUILD_CLAR "Build Tests using the Clar suite" ON) +OPTION (BUILD_EXAMPLES "Build library usage example apps" OFF) +OPTION (TAGS "Generate tags" OFF) +OPTION (PROFILE "Generate profiling information" OFF) -# Build Release by default +# Platform specific compilation flags +IF (MSVC) + # Not using __stdcall with the CRT causes problems + OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON) + + SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}") + IF (STDCALL) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz") + ENDIF () + SET(CMAKE_C_FLAGS_DEBUG "/Od /DEBUG /MTd /RTC1 /RTCs /RTCu") + SET(CMAKE_C_FLAGS_RELEASE "/MT /O2") + SET(WIN_RC "src/win32/git2.rc") + + # Precompiled headers +ELSE () + SET(CMAKE_C_FLAGS "-O2 -g -D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") + SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") + IF (MINGW) # MinGW always does PIC and complains if we tell it to + STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") + ELSE () + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -fPIC") + ENDIF () + IF (PROFILE) + SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}") + SET(CMAKE_EXE_LINKER_FLAGS "-pg ${CMAKE_EXE_LINKER_FLAGS}") + ENDIF () +ENDIF() + +# Build Debug by default IF (NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) + SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF () +FIND_PACKAGE(OpenSSL) +IF (OPENSSL_FOUND) + ADD_DEFINITIONS(-DGIT_SSL) + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) +ENDIF() + IF (THREADSAFE) IF (NOT WIN32) find_package(Threads REQUIRED) @@ -79,57 +106,107 @@ IF (THREADSAFE) ADD_DEFINITIONS(-DGIT_THREADS) ENDIF() +ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) + # Collect sourcefiles -FILE(GLOB SRC src/*.c src/backends/*.c) -FILE(GLOB SRC_ZLIB deps/zlib/*.c) -FILE(GLOB SRC_SHA1 src/block-sha1/*.c) -FILE(GLOB SRC_PLAT src/unix/*.c) FILE(GLOB SRC_H include/git2/*.h) # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) - ADD_DEFINITIONS(-DWIN32 -D_DEBUG -D_LIB) - FILE(GLOB SRC_PLAT src/win32/*.c) -ENDIF () - -# Specify sha1 implementation -IF (SHA1_TYPE STREQUAL "ppc") - ADD_DEFINITIONS(-DPPC_SHA1) - FILE(GLOB SRC_SHA1 src/ppc/*.c) -ELSEIF (SHA1_TYPE STREQUAL "openssl") - ADD_DEFINITIONS(-DOPENSSL_SHA1) - SET (SRC_SHA1) - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) - SET (LIB_SHA1 ${OPENSSL_CRYPTO_LIBRARIES}) + ADD_DEFINITIONS(-DWIN32 -D_DEBUG -D_WIN32_WINNT=0x0501) + FILE(GLOB SRC src/*.c src/transports/*.c src/xdiff/*.c src/win32/*.c src/compat/*.c) +ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + FILE(GLOB SRC src/*.c src/transports/*.c src/xdiff/*.c src/unix/*.c src/compat/*.c) +ELSE() + FILE(GLOB SRC src/*.c src/transports/*.c src/xdiff/*.c src/unix/*.c) ENDIF () # Compile and link libgit2 -ADD_LIBRARY(git2 ${SRC} ${SRC_PLAT} ${SRC_SHA1} ${SRC_ZLIB}) -TARGET_LINK_LIBRARIES(git2 ${LIB_SHA1} ${CMAKE_THREAD_LIBS_INIT} ${SQLITE3_LIBRARIES}) +ADD_LIBRARY(git2 ${SRC} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${WIN_RC}) + +IF (WIN32) + TARGET_LINK_LIBRARIES(git2 ws2_32) +ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + TARGET_LINK_LIBRARIES(git2 socket nsl) +ENDIF () + +TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) # Install -INSTALL(TARGETS git2 - RUNTIME DESTINATION ${INSTALL_BIN} - LIBRARY DESTINATION ${INSTALL_LIB} - ARCHIVE DESTINATION ${INSTALL_LIB} +INSTALL(TARGETS git2 + RUNTIME DESTINATION ${INSTALL_BIN} + LIBRARY DESTINATION ${INSTALL_LIB} + ARCHIVE DESTINATION ${INSTALL_LIB} ) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${INSTALL_LIB}/pkgconfig ) INSTALL(DIRECTORY include/git2 DESTINATION ${INSTALL_INC} ) INSTALL(FILES include/git2.h DESTINATION ${INSTALL_INC} ) # Tests -IF (BUILD_TESTS) - SET(TEST_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.") - ADD_DEFINITIONS(-DTEST_RESOURCES=\"${TEST_RESOURCES}\") - - ENABLE_TESTING() - INCLUDE_DIRECTORIES(tests) +IF (BUILD_CLAR) - FILE(GLOB SRC_TEST tests/t??-*.c) + FIND_PACKAGE(PythonInterp REQUIRED) - ADD_EXECUTABLE(libgit2_test tests/test_main.c tests/test_lib.c tests/test_helpers.c ${SRC} ${SRC_PLAT} ${SRC_SHA1} ${SRC_TEST} ${SRC_ZLIB}) - TARGET_LINK_LIBRARIES(libgit2_test ${LIB_SHA1} ${CMAKE_THREAD_LIBS_INIT} ${SQLITE3_LIBRARIES}) + 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.") + ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") + ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") - ADD_TEST(libgit2_test libgit2_test) + INCLUDE_DIRECTORIES(${CLAR_PATH}) + FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/clar_helpers.c ${CLAR_PATH}/testlib.c) + + ADD_CUSTOM_COMMAND( + OUTPUT ${CLAR_PATH}/clar_main.c ${CLAR_PATH}/clar.h + COMMAND ${PYTHON_EXECUTABLE} clar -vtap . + DEPENDS ${CLAR_PATH}/clar ${SRC_TEST} + WORKING_DIRECTORY ${CLAR_PATH} + ) + ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX}) + TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) + IF (WIN32) + TARGET_LINK_LIBRARIES(libgit2_clar ws2_32) + ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + TARGET_LINK_LIBRARIES(libgit2_clar socket nsl) + ENDIF () + + ENABLE_TESTING() + ADD_TEST(libgit2_clar libgit2_clar) +ENDIF () + +IF (TAGS) + FIND_PROGRAM(CTAGS ctags) + IF (NOT CTAGS) + message(FATAL_ERROR "Could not find ctags command") + ENDIF () + + FILE(GLOB_RECURSE SRC_ALL *.[ch]) + + ADD_CUSTOM_COMMAND( + OUTPUT tags + COMMAND ${CTAGS} -a ${SRC_ALL} + DEPENDS ${SRC_ALL} + ) + ADD_CUSTOM_TARGET( + do_tags ALL + DEPENDS tags + ) +ENDIF () + +IF (BUILD_EXAMPLES) + FILE(GLOB_RECURSE EXAMPLE_SRC examples/network/*.c) + ADD_EXECUTABLE(cgit2 ${EXAMPLE_SRC}) + TARGET_LINK_LIBRARIES(cgit2 git2 pthread) + + 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) ENDIF () diff --git a/CONVENTIONS b/CONVENTIONS index 575cdc563..f082d8e6c 100644 --- a/CONVENTIONS +++ b/CONVENTIONS @@ -49,7 +49,7 @@ Functions should prefer to return a 'int' to indicate success or failure and supply any output through the first argument (or first few arguments if multiple outputs are supplied). -int status codes are 0 for GIT_SUCCESS and < 0 for an error. +int status codes are 0 for GIT_OK and < 0 for an error. This permits common POSIX result testing: ---- @@ -58,7 +58,7 @@ This permits common POSIX result testing: ---- Functions returning a pointer may return NULL instead of an int -if there is only one type of failure (ENOMEM). +if there is only one type of failure (GIT_ENOMEM). Functions returning a pointer may also return NULL if the common case needed by the application is strictly success/failure and a diff --git a/COPYING b/COPYING index c36f4cf1e..e3f9969d0 100644 --- a/COPYING +++ b/COPYING @@ -1,11 +1,17 @@ + libgit2 is Copyright (C) 2009-2012 the libgit2 contributors, + unless otherwise stated. See the AUTHORS file for details. Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. +---------------------------------------------------------------------- + + LINKING EXCEPTION + In addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled - version of this file into combinations with other programs, + version of this library into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover @@ -71,7 +77,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -126,7 +132,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -184,7 +190,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -241,7 +247,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -294,7 +300,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest @@ -354,3 +360,571 @@ proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. + +---------------------------------------------------------------------- + +The bundled ZLib code is licensed under the ZLib license: + +Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +---------------------------------------------------------------------- + +The priority queue implementation is based on code licensed under the +Apache 2.0 license: + + Copyright 2010 Volkan Yazıcı + Copyright 2006-2010 The Apache Software Foundation + +The full text of the Apache 2.0 license is available at: + + http://www.apache.org/licenses/LICENSE-2.0 + +---------------------------------------------------------------------- + +The Clay framework is licensed under the MIT license: + +Copyright (C) 2011 by Vicent Marti + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------- + +The regex library (deps/regex/) is licensed under the GNU LGPL + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Makefile.embed b/Makefile.embed new file mode 100644 index 000000000..65f13b9b6 --- /dev/null +++ b/Makefile.embed @@ -0,0 +1,26 @@ +rm=rm -f +CC=cc +AR=ar cq +RANLIB=ranlib +LIBNAME=libgit2.a + +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 -fPIC -O2 $(EXTRA_CFLAGS) + +SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/unix/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) +OBJS = $(patsubst %.c,%.o,$(SRCS)) + +%.c.o: + $(CC) $(CFLAGS) -c $*.c + +all: $(LIBNAME) + +$(LIBNAME): $(OBJS) + $(rm) $@ + $(AR) $@ $(OBJS) + $(RANLIB) $@ + +clean: + $(rm) $(OBJS) $(LIBNAME) diff --git a/README.md b/README.md index 7cfde59f8..4c23fc870 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ 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 re-entrant linkable library with a solid API, allowing you to write native speed custom Git applications in any language with bindings. @@ -10,8 +12,9 @@ This basically means that you can link it (unmodified) with any kind of software release its source code. * Mailing list: + * Archives: * Website: -* API documentation: +* API documentation: * Usage guide: What It Can Do @@ -20,85 +23,27 @@ What It Can Do libgit2 is already very usable. * SHA conversions, formatting and shortening -* object reading (loose and packed) -* object writing (loose) -* commit, tag, tree and blob parsing and write-back +* abstracted ODB backend system +* commit, tag, tree and blob parsing, editing, and write-back * tree traversal * revision walking * index file (staging area) manipulation -* custom ODB backends * reference management (including packed references) -* ...and more - - -Building libgit2 - 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. - -Additionally, the following libraries may be used as replacement for built-in functionality: - -* LibSSL **(optional)** - -libgit2 can be built using the SHA1 implementation of LibSSL-Crypto, instead of the built-in custom implementations. Performance wise, they are quite similar. - -Building libgit2 - Using waf -====================== - -Waf is a minimalist build system which only requires a Python 2.5+ interpreter to run. This is the default build system for libgit2. - -To build libgit2 using waf, first configure the build system by running: - - $ ./waf configure - -Then build the library, either in its shared (libgit2.so) or static form (libgit2.a): - - $ ./waf build-static - $ ./waf build-shared - -You can then run the full test suite with: - - $ ./waf test - -And finally you can install the library with (you may need to sudo): - - $ sudo ./waf install - -The waf build system for libgit2 accepts the following flags: - - --debug - build the library with debug symbols. - Defaults to off. - - --sha1=[builtin|ppc|openssl] - use the builtin SHA1 functions, the optimized PPC versions - or the SHA1 functions from LibCrypto (OpenSSL). - Defaults to 'builtin'. - - --msvc=[7.1|8.0|9.0|10.0] - Force a specific version of the MSVC compiler, if more than - one version is installed. - - --arch=[ia64|x64|x86|x86_amd64|x86_ia64] - Force a specific architecture for compilers that support it. - - --with-sqlite - Enable sqlite support. - - --with-redis - Enable redis support. - -You can run `./waf --help` to see a full list of install options and -targets. - +* config file management +* high level repository management +* thread safety and reentrancy +* descriptive and detailed error messages +* ...and more (over 175 different API calls) Building libgit2 - Using CMake ============================== -The libgit2 library can also be built using CMake 2.6+ () on all platforms. +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. On most systems you can build the library using the following commands @@ -113,36 +58,75 @@ To install the library you can specify the install prefix by setting: $ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix $ cmake --build . --target install +If you want to build a universal binary for Mac OS X, CMake sets it +all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` +when configuring. + For more advanced use or questions about CMake please read . +The following CMake variables are declared: + +- `INSTALL_BIN`: Where to install binaries to. +- `INSTALL_LIB`: Where to install libraries to. +- `INSTALL_INC`: Where to install headers to. +- `BUILD_SHARED_LIBS`: Build libgit2 as a Shared Library (defaults to ON) +- `BUILD_CLAR`: Build [Clar](https://github.com/tanoku/clar)-based test suite (defaults to ON) +- `THREADSAFE`: Build libgit2 with threading support (defaults to OFF) Language Bindings ================================== Here are the bindings to libgit2 that are currently available: -* Rugged (Ruby bindings) -* objective-git (Objective-C bindings) -* pygit2 (Python bindings) -* libgit2sharp (.NET bindings) -* php-git (PHP bindings) -* luagit2 (Lua bindings) -* GitForDelphi (Delphi bindings) -* node-gitteh (Node.js bindings) -* nodegit (Node.js bindings) -* go-git (Go bindings) -* libqgit2 (C++ QT bindings) -* libgit2-ocaml (ocaml bindings) -* Geef (Erlang bindings) +* C++ + * libqgit2, Qt bindings +* Chicken Scheme + * chicken-git +* Delphi + * GitForDelphi +* Erlang + * Geef +* Go + * go-git +* GObject + * libgit2-glib +* Haskell + * hgit2 +* Lua + * luagit2 +* .NET + * libgit2net, low level bindings + * libgit2sharp +* Node.js + * node-gitteh + * nodegit +* Objective-C + * objective-git +* OCaml + * libgit2-ocaml +* Parrot Virtual Machine + * parrot-libgit2 +* Perl + * git-xs-pm +* PHP + * php-git +* Python + * pygit2 +* Ruby + * Rugged +* Vala + * libgit2.vapi If you start another language binding to libgit2, please let us know so we can add it to the list. -How Can I Contribute +How Can I Contribute? ================================== Fork libgit2/libgit2 on GitHub, add your improvement, push it to a branch -in your fork named for the topic, send a pull request. +in your fork named for the topic, send a pull request. If you change the +API or make other large changes, make a note of it in docs/rel-notes/ in a +file named after the next release. You can also file bugs or feature requests under the libgit2 project on GitHub, or join us on the mailing list by sending an email to: @@ -150,7 +134,7 @@ GitHub, or join us on the mailing list by sending an email to: libgit2@librelist.com -License +License ================================== libgit2 is under GPL2 **with linking exemption**. This means you can link to the library with any program, commercial, open source or diff --git a/api.docurium b/api.docurium new file mode 100644 index 000000000..9e17817db --- /dev/null +++ b/api.docurium @@ -0,0 +1,13 @@ +{ + "name": "libgit2", + "github": "libgit2/libgit2", + "input": "include/git2", + "prefix": "git_", + "output": "docs", + "branch": "gh-pages", + "examples": "examples", + "legacy": { + "input": {"src/git": ["v0.1.0"], + "src/git2": ["v0.2.0", "v0.3.0"]} + } +} diff --git a/api.doxygen b/api.doxygen deleted file mode 100644 index b812add85..000000000 --- a/api.doxygen +++ /dev/null @@ -1,24 +0,0 @@ -PROJECT_NAME = libgit2 - -INPUT = include/git2 -QUIET = YES -RECURSIVE = YES -FILE_PATTERNS = *.h -OUTPUT_DIRECTORY = apidocs -GENERATE_TAGFILE = apidocs/libgit2.tag - -JAVADOC_AUTOBRIEF = YES -MACRO_EXPANSION = YES -EXPAND_ONLY_PREDEF = YES -OPTIMIZE_OUTPUT_FOR_C = YES -STRIP_CODE_COMMENTS = NO -FULL_PATH_NAMES = NO -CASE_SENSE_NAMES = NO - -PREDEFINED = \ - "GIT_EXTERN(x)=x" \ - "GIT_EXTERN_TLS(x)=x" \ - "GIT_INLINE(x)=x" \ - "GIT_BEGIN_DECL=" \ - "GIT_END_DECL=" \ - DOXYGEN= diff --git a/deps/http-parser/LICENSE-MIT b/deps/http-parser/LICENSE-MIT new file mode 100644 index 000000000..58010b388 --- /dev/null +++ b/deps/http-parser/LICENSE-MIT @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/deps/http-parser/http_parser.c b/deps/http-parser/http_parser.c new file mode 100644 index 000000000..438e81bec --- /dev/null +++ b/deps/http-parser/http_parser.c @@ -0,0 +1,1778 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include + + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#if HTTP_PARSER_DEBUG +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ + parser->error_lineno = __LINE__; \ +} while (0) +#else +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) +#endif + + +#define CALLBACK2(FOR) \ +do { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + return (p - data); \ + } \ + } \ +} while (0) + + +#define MARK(FOR) \ +do { \ + FOR##_mark = p; \ +} while (0) + +#define CALLBACK(FOR) \ +do { \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, \ + FOR##_mark, \ + p - FOR##_mark)) \ + { \ + SET_ERRNO(HPE_CB_##FOR); \ + return (p - data); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { "DELETE" + , "GET" + , "HEAD" + , "POST" + , "PUT" + , "CONNECT" + , "OPTIONS" + , "TRACE" + , "COPY" + , "LOCK" + , "MKCOL" + , "MOVE" + , "PROPFIND" + , "PROPPATCH" + , "UNLOCK" + , "REPORT" + , "MKACTIVITY" + , "CHECKOUT" + , "MERGE" + , "M-SEARCH" + , "NOTIFY" + , "SUBSCRIBE" + , "UNSUBSCRIBE" + , "PATCH" + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + ' ', '!', '"', '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', '/', +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', '}', '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +static const uint8_t normal_url_char[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, 1, 1, 0, 1, 1, 1, 1, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1, 1, 1, 1, 1, 1, 1, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1, 1, 1, 1, 1, 1, 1, 0, }; + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_host + , s_req_port + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + /* Important: 's_headers_almost_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + }; + + +#define PARSING_HEADER(state) (state <= s_headers_almost_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) + +#if HTTP_PARSER_STRICT +#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define IS_URL_CHAR(c) \ + (normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data, *pe; + size_t to_read; + enum state state; + enum header_states header_state; + size_t index = parser->index; + size_t nread = parser->nread; + const char *header_field_mark, *header_value_mark, *url_mark; + const char *matcher; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + state = (enum state) parser->state; + header_state = (enum header_states) parser->header_state; + + if (len == 0) { + switch (state) { + case s_body_identity_eof: + CALLBACK2(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + /* technically we could combine all of these (except for url_mark) into one + variable, saving stack space, but it seems more clear to have them + separated. */ + header_field_mark = 0; + header_value_mark = 0; + url_mark = 0; + + if (state == s_header_field) + header_field_mark = data; + if (state == s_header_value) + header_value_mark = data; + if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash + || state == s_req_schema_slash_slash || state == s_req_port + || state == s_req_query_string_start || state == s_req_query_string + || state == s_req_host + || state == s_req_fragment_start || state == s_req_fragment) + url_mark = data; + + for (p=data, pe=data+len; p != pe; p++) { + ch = *p; + + if (PARSING_HEADER(state)) { + ++nread; + /* Buffer overflow attack */ + if (nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + switch (state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (ch == 'H') + state = s_res_or_resp_H; + else { + parser->type = HTTP_REQUEST; + goto start_req_method_assign; + } + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + index = 2; + state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + switch (ch) { + case 'H': + state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + state = s_res_status; + break; + case CR: + state = s_res_line_almost_done; + break; + case LF: + state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + start_req_method_assign: + parser->method = (enum http_method) 0; + index = 1; + switch (ch) { + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND or PROPPATCH or PUT or PATCH */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + state = s_req_method; + break; + } + + case s_req_method: + { + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[index] == '\0') { + state = s_req_spaces_before_url; + } else if (ch == matcher[index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + goto error; + } + } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') { + parser->method = HTTP_UNSUBSCRIBE; + } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++index; + break; + } + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + if (ch == '/' || ch == '*') { + MARK(url); + state = s_req_path; + break; + } + + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * CONNECT is followed by a hostname, which begins with alphanum. + * All other methods are followed by '/' or '*' (handled above). + */ + if (IS_ALPHA(ch) || (parser->method == HTTP_CONNECT && IS_NUM(ch))) { + MARK(url); + state = (parser->method == HTTP_CONNECT) ? s_req_host : s_req_schema; + break; + } + + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + case s_req_schema: + { + if (IS_ALPHA(ch)) break; + + if (ch == ':') { + state = s_req_schema_slash; + break; + } + + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + case s_req_schema_slash: + STRICT_CHECK(ch != '/'); + state = s_req_schema_slash_slash; + break; + + case s_req_schema_slash_slash: + STRICT_CHECK(ch != '/'); + state = s_req_host; + break; + + case s_req_host: + { + if (IS_HOST_CHAR(ch)) break; + switch (ch) { + case ':': + state = s_req_port; + break; + case '/': + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + case '?': + state = s_req_query_string_start; + break; + default: + SET_ERRNO(HPE_INVALID_HOST); + goto error; + } + break; + } + + case s_req_port: + { + if (IS_NUM(ch)) break; + switch (ch) { + case '/': + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com:1234 HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + case '?': + state = s_req_query_string_start; + break; + default: + SET_ERRNO(HPE_INVALID_PORT); + goto error; + } + break; + } + + case s_req_path: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + state = s_req_query_string_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_PATH); + goto error; + } + break; + } + + case s_req_query_string_start: + { + if (IS_URL_CHAR(ch)) { + state = s_req_query_string; + break; + } + + switch (ch) { + case '?': + break; /* XXX ignore extra '?' ... is this right? */ + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); + goto error; + } + break; + } + + case s_req_query_string: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + break; + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); + goto error; + } + break; + } + + case s_req_fragment_start: + { + if (IS_URL_CHAR(ch)) { + state = s_req_fragment; + break; + } + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + state = s_req_fragment; + break; + case '#': + break; + default: + SET_ERRNO(HPE_INVALID_FRAGMENT); + goto error; + } + break; + } + + case s_req_fragment: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + case '#': + break; + default: + SET_ERRNO(HPE_INVALID_FRAGMENT); + goto error; + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + state = s_header_field_start; + break; + } + + case s_header_field_start: + header_field_start: + { + if (ch == CR) { + state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + state = s_headers_almost_done; + goto headers_almost_done; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + index = 0; + state = s_header_field; + + switch (c) { + case 'c': + header_state = h_C; + break; + + case 'p': + header_state = h_matching_proxy_connection; + break; + + case 't': + header_state = h_matching_transfer_encoding; + break; + + case 'u': + header_state = h_matching_upgrade; + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (header_state) { + case h_general: + break; + + case h_C: + index++; + header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + index++; + header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + index++; + switch (c) { + case 'n': + header_state = h_matching_connection; + break; + case 't': + header_state = h_matching_content_length; + break; + default: + header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + index++; + if (index > sizeof(CONNECTION)-1 + || c != CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + index++; + if (index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(PROXY_CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + index++; + if (index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[index]) { + header_state = h_general; + } else if (index == sizeof(CONTENT_LENGTH)-2) { + header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + index++; + if (index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[index]) { + header_state = h_general; + } else if (index == sizeof(TRANSFER_ENCODING)-2) { + header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + index++; + if (index > sizeof(UPGRADE)-1 + || c != UPGRADE[index]) { + header_state = h_general; + } else if (index == sizeof(UPGRADE)-2) { + header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + CALLBACK(header_field); + state = s_header_value_start; + break; + } + + if (ch == CR) { + state = s_header_almost_done; + CALLBACK(header_field); + break; + } + + if (ch == LF) { + CALLBACK(header_field); + state = s_header_field_start; + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_start: + { + if (ch == ' ' || ch == '\t') break; + + MARK(header_value); + + state = s_header_value; + index = 0; + + if (ch == CR) { + CALLBACK(header_value); + header_state = h_general; + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + state = s_header_field_start; + break; + } + + c = LOWER(ch); + + switch (header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + header_state = h_matching_transfer_encoding_chunked; + } else { + header_state = h_general; + } + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + header_state = h_matching_connection_close; + } else { + header_state = h_general; + } + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + CALLBACK(header_value); + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + goto header_almost_done; + } + + c = LOWER(ch); + + switch (header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + if (ch == ' ') break; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length *= 10; + parser->content_length += ch - '0'; + break; + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + index++; + if (index > sizeof(CHUNKED)-1 + || c != CHUNKED[index]) { + header_state = h_general; + } else if (index == sizeof(CHUNKED)-2) { + header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + index++; + if (index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[index]) { + header_state = h_general; + } else if (index == sizeof(KEEP_ALIVE)-2) { + header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + index++; + if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) { + header_state = h_general; + } else if (index == sizeof(CLOSE)-2) { + header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') header_state = h_general; + break; + + default: + state = s_header_value; + header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + header_almost_done: + { + STRICT_CHECK(ch != LF); + + state = s_header_value_lws; + + switch (header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + state = s_header_value_start; + else + { + state = s_header_field_start; + goto header_field_start; + } + break; + } + + case s_headers_almost_done: + headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + break; + } + + nread = 0; + + if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) { + parser->upgrade = 1; + } + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + parser->state = state; + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + CALLBACK2(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->content_length > 0) { + /* Content-Length header given and non-zero */ + state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) { + /* Assume content-length 0 - read the next */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else { + /* Read body until EOF */ + state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + to_read = (size_t)MIN(pe - p, parser->content_length); + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + parser->content_length -= to_read; + if (parser->content_length == 0) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } + } + break; + + /* read until EOF */ + case s_body_identity_eof: + to_read = pe - p; + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + break; + + case s_chunk_size_start: + { + assert(nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + state = s_chunk_size; + break; + } + + case s_chunk_size: + { + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length *= 16; + parser->content_length += unhex_val; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + state = s_header_field_start; + } else { + state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + assert(parser->flags & F_CHUNKED); + + to_read = (size_t)MIN(pe - p, parser->content_length); + + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + + if ((signed)to_read == parser->content_length) { + state = s_chunk_data_almost_done; + } + + parser->content_length -= to_read; + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != CR); + state = s_chunk_data_done; + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + CALLBACK(header_field); + CALLBACK(header_value); + CALLBACK(url); + + parser->state = state; + parser->header_state = header_state; + parser->index = (unsigned char) index; + parser->nread = nread; + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +int +http_should_keep_alive (http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } else { + return 1; + } + } else { + /* HTTP/1.0 or earlier */ + if (parser->flags & F_CONNECTION_KEEP_ALIVE) { + return 1; + } else { + return 0; + } + } +} + + +const char * http_method_str (enum http_method m) +{ + return method_strings[m]; +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->nread = 0; + parser->upgrade = 0; + parser->flags = 0; + parser->method = 0; + parser->http_errno = 0; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} diff --git a/deps/http-parser/http_parser.h b/deps/http-parser/http_parser.h new file mode 100644 index 000000000..b6f6e9978 --- /dev/null +++ b/deps/http-parser/http_parser.h @@ -0,0 +1,267 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_PARSER_VERSION_MAJOR 1 +#define HTTP_PARSER_VERSION_MINOR 0 + +#ifdef _MSC_VER + /* disable silly warnings */ +# pragma warning(disable: 4127 4214) +#endif + +#include +#include "git2/common.h" + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to + * the error reporting facility. + */ +#ifndef HTTP_PARSER_DEBUG +# define HTTP_PARSER_DEBUG 0 +#endif + + +/* Maximium header size allowed */ +#define HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; +typedef struct http_parser_result http_parser_result; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_path" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +enum http_method + { HTTP_DELETE = 0 + , HTTP_GET + , HTTP_HEAD + , HTTP_POST + , HTTP_PUT + /* pathological */ + , HTTP_CONNECT + , HTTP_OPTIONS + , HTTP_TRACE + /* webdav */ + , HTTP_COPY + , HTTP_LOCK + , HTTP_MKCOL + , HTTP_MOVE + , HTTP_PROPFIND + , HTTP_PROPPATCH + , HTTP_UNLOCK + /* subversion */ + , HTTP_REPORT + , HTTP_MKACTIVITY + , HTTP_CHECKOUT + , HTTP_MERGE + /* upnp */ + , HTTP_MSEARCH + , HTTP_NOTIFY + , HTTP_SUBSCRIBE + , HTTP_UNSUBSCRIBE + /* RFC-5789 */ + , HTTP_PATCH + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_path, "the on_path callback failed") \ + XX(CB_query_string, "the on_query_string callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_fragment, "the on_fragment callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + +/* Get the line number that generated the current error */ +#if HTTP_PARSER_DEBUG +#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) +#else +#define HTTP_PARSER_ERRNO_LINE(p) 0 +#endif + + +struct http_parser { + /** PRIVATE **/ + unsigned char type : 2; + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; + unsigned char header_state; + unsigned char index; + + size_t nread; + int64_t content_length; + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + unsigned char http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned char upgrade : 1; + +#if HTTP_PARSER_DEBUG + uint32_t error_lineno; +#endif + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/deps/regex/config.h b/deps/regex/config.h new file mode 100644 index 000000000..95370690e --- /dev/null +++ b/deps/regex/config.h @@ -0,0 +1,7 @@ +#ifndef _REGEX_CONFIG_H_ +#define _REGEX_CONFIG_H_ + +# define GAWK +# define NO_MBSUPPORT + +#endif diff --git a/deps/regex/regcomp.c b/deps/regex/regcomp.c new file mode 100644 index 000000000..7373fbc22 --- /dev/null +++ b/deps/regex/regcomp.c @@ -0,0 +1,3856 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002-2007,2009,2010 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, + size_t length, reg_syntax_t syntax); +static void re_compile_fastmap_iter (regex_t *bufp, + const re_dfastate_t *init_state, + char *fastmap); +static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len); +#ifdef RE_ENABLE_I18N +static void free_charset (re_charset_t *cset); +#endif /* RE_ENABLE_I18N */ +static void free_workarea_compile (regex_t *preg); +static reg_errcode_t create_initial_state (re_dfa_t *dfa); +#ifdef RE_ENABLE_I18N +static void optimize_utf8 (re_dfa_t *dfa); +#endif +static reg_errcode_t analyze (regex_t *preg); +static reg_errcode_t preorder (bin_tree_t *root, + reg_errcode_t (fn (void *, bin_tree_t *)), + void *extra); +static reg_errcode_t postorder (bin_tree_t *root, + reg_errcode_t (fn (void *, bin_tree_t *)), + void *extra); +static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node); +static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node); +static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg, + bin_tree_t *node); +static reg_errcode_t calc_first (void *extra, bin_tree_t *node); +static reg_errcode_t calc_next (void *extra, bin_tree_t *node); +static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node); +static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint); +static int search_duplicated_node (const re_dfa_t *dfa, int org_node, + unsigned int constraint); +static reg_errcode_t calc_eclosure (re_dfa_t *dfa); +static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, + int node, int root); +static reg_errcode_t calc_inveclosure (re_dfa_t *dfa); +static int fetch_number (re_string_t *input, re_token_t *token, + reg_syntax_t syntax); +static int peek_token (re_token_t *token, re_string_t *input, + reg_syntax_t syntax) internal_function; +static bin_tree_t *parse (re_string_t *regexp, regex_t *preg, + reg_syntax_t syntax, reg_errcode_t *err); +static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg, + re_token_t *token, reg_syntax_t syntax, + int nest, reg_errcode_t *err); +static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp, + re_dfa_t *dfa, re_token_t *token, + reg_syntax_t syntax, reg_errcode_t *err); +static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, + re_token_t *token, reg_syntax_t syntax, + reg_errcode_t *err); +static reg_errcode_t parse_bracket_element (bracket_elem_t *elem, + re_string_t *regexp, + re_token_t *token, int token_len, + re_dfa_t *dfa, + reg_syntax_t syntax, + int accept_hyphen); +static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem, + re_string_t *regexp, + re_token_t *token); +#ifdef RE_ENABLE_I18N +static reg_errcode_t build_equiv_class (bitset_t sbcset, + re_charset_t *mbcset, + int *equiv_class_alloc, + const unsigned char *name); +static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, + bitset_t sbcset, + re_charset_t *mbcset, + int *char_class_alloc, + const char *class_name, + reg_syntax_t syntax); +#else /* not RE_ENABLE_I18N */ +static reg_errcode_t build_equiv_class (bitset_t sbcset, + const unsigned char *name); +static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans, + bitset_t sbcset, + const char *class_name, + reg_syntax_t syntax); +#endif /* not RE_ENABLE_I18N */ +static bin_tree_t *build_charclass_op (re_dfa_t *dfa, + RE_TRANSLATE_TYPE trans, + const char *class_name, + const char *extra, + int non_match, reg_errcode_t *err); +static bin_tree_t *create_tree (re_dfa_t *dfa, + bin_tree_t *left, bin_tree_t *right, + re_token_type_t type); +static bin_tree_t *create_token_tree (re_dfa_t *dfa, + bin_tree_t *left, bin_tree_t *right, + const re_token_t *token); +static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa); +static void free_token (re_token_t *node); +static reg_errcode_t free_tree (void *extra, bin_tree_t *node); +static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node); + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. + POSIX doesn't require that we do anything for REG_NOERROR, + but why not be nice? */ + +const char __re_error_msgid[] attribute_hidden = + { +#define REG_NOERROR_IDX 0 + gettext_noop ("Success") /* REG_NOERROR */ + "\0" +#define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success") + gettext_noop ("No match") /* REG_NOMATCH */ + "\0" +#define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match") + gettext_noop ("Invalid regular expression") /* REG_BADPAT */ + "\0" +#define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression") + gettext_noop ("Invalid collation character") /* REG_ECOLLATE */ + "\0" +#define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character") + gettext_noop ("Invalid character class name") /* REG_ECTYPE */ + "\0" +#define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name") + gettext_noop ("Trailing backslash") /* REG_EESCAPE */ + "\0" +#define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash") + gettext_noop ("Invalid back reference") /* REG_ESUBREG */ + "\0" +#define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference") + gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */ + "\0" +#define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^") + gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */ + "\0" +#define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(") + gettext_noop ("Unmatched \\{") /* REG_EBRACE */ + "\0" +#define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{") + gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */ + "\0" +#define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}") + gettext_noop ("Invalid range end") /* REG_ERANGE */ + "\0" +#define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end") + gettext_noop ("Memory exhausted") /* REG_ESPACE */ + "\0" +#define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted") + gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */ + "\0" +#define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression") + gettext_noop ("Premature end of regular expression") /* REG_EEND */ + "\0" +#define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression") + gettext_noop ("Regular expression too big") /* REG_ESIZE */ + "\0" +#define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big") + gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */ + }; + +const size_t __re_error_msgid_idx[] attribute_hidden = + { + REG_NOERROR_IDX, + REG_NOMATCH_IDX, + REG_BADPAT_IDX, + REG_ECOLLATE_IDX, + REG_ECTYPE_IDX, + REG_EESCAPE_IDX, + REG_ESUBREG_IDX, + REG_EBRACK_IDX, + REG_EPAREN_IDX, + REG_EBRACE_IDX, + REG_BADBR_IDX, + REG_ERANGE_IDX, + REG_ESPACE_IDX, + REG_BADRPT_IDX, + REG_EEND_IDX, + REG_ESIZE_IDX, + REG_ERPAREN_IDX + }; + +/* Entry points for GNU code. */ + + +#ifdef ZOS_USS + +/* For ZOS USS we must define btowc */ + +wchar_t +btowc (int c) +{ + wchar_t wtmp[2]; + char tmp[2]; + + tmp[0] = c; + tmp[1] = 0; + + mbtowc (wtmp, tmp, 1); + return wtmp[0]; +} +#endif + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length LENGTH) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. */ + +const char * +re_compile_pattern (const char *pattern, + size_t length, + struct re_pattern_buffer *bufp) +{ + reg_errcode_t ret; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub, unless RE_NO_SUB is set. */ + bufp->no_sub = !!(re_syntax_options & RE_NO_SUB); + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = re_compile_internal (bufp, pattern, length, re_syntax_options); + + if (!ret) + return NULL; + return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); +} +#ifdef _LIBC +weak_alias (__re_compile_pattern, re_compile_pattern) +#endif + +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can + also be assigned to arbitrarily: each pattern buffer stores its own + syntax, so it can be changed between regex compilations. */ +/* This has no initializer because initialized variables in Emacs + become read-only after dumping. */ +reg_syntax_t re_syntax_options; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +reg_syntax_t +re_set_syntax (reg_syntax_t syntax) +{ + reg_syntax_t ret = re_syntax_options; + + re_syntax_options = syntax; + return ret; +} +#ifdef _LIBC +weak_alias (__re_set_syntax, re_set_syntax) +#endif + +int +re_compile_fastmap (struct re_pattern_buffer *bufp) +{ + re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; + char *fastmap = bufp->fastmap; + + memset (fastmap, '\0', sizeof (char) * SBC_MAX); + re_compile_fastmap_iter (bufp, dfa->init_state, fastmap); + if (dfa->init_state != dfa->init_state_word) + re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap); + if (dfa->init_state != dfa->init_state_nl) + re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap); + if (dfa->init_state != dfa->init_state_begbuf) + re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap); + bufp->fastmap_accurate = 1; + return 0; +} +#ifdef _LIBC +weak_alias (__re_compile_fastmap, re_compile_fastmap) +#endif + +static inline void +__attribute ((always_inline)) +re_set_fastmap (char *fastmap, int icase, int ch) +{ + fastmap[ch] = 1; + if (icase) + fastmap[tolower (ch)] = 1; +} + +/* Helper function for re_compile_fastmap. + Compile fastmap for the initial_state INIT_STATE. */ + +static void +re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state, + char *fastmap) +{ + volatile re_dfa_t *dfa = (re_dfa_t *) bufp->buffer; + int node_cnt; + int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE)); + for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt) + { + int node = init_state->nodes.elems[node_cnt]; + re_token_type_t type = dfa->nodes[node].type; + + if (type == CHARACTER) + { + re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c); +#ifdef RE_ENABLE_I18N + if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) + { + unsigned char *buf = re_malloc (unsigned char, dfa->mb_cur_max), *p; + wchar_t wc; + mbstate_t state; + + p = buf; + *p++ = dfa->nodes[node].opr.c; + while (++node < dfa->nodes_len + && dfa->nodes[node].type == CHARACTER + && dfa->nodes[node].mb_partial) + *p++ = dfa->nodes[node].opr.c; + memset (&state, '\0', sizeof (state)); + if (__mbrtowc (&wc, (const char *) buf, p - buf, + &state) == p - buf + && (__wcrtomb ((char *) buf, towlower (wc), &state) + != (size_t) -1)) + re_set_fastmap (fastmap, 0, buf[0]); + re_free (buf); + } +#endif + } + else if (type == SIMPLE_BRACKET) + { + int i, ch; + for (i = 0, ch = 0; i < BITSET_WORDS; ++i) + { + int j; + bitset_word_t w = dfa->nodes[node].opr.sbcset[i]; + for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) + if (w & ((bitset_word_t) 1 << j)) + re_set_fastmap (fastmap, icase, ch); + } + } +#ifdef RE_ENABLE_I18N + else if (type == COMPLEX_BRACKET) + { + re_charset_t *cset = dfa->nodes[node].opr.mbcset; + int i; + +# ifdef _LIBC + /* See if we have to try all bytes which start multiple collation + elements. + e.g. In da_DK, we want to catch 'a' since "aa" is a valid + collation element, and don't catch 'b' since 'b' is + the only collation element which starts from 'b' (and + it is caught by SIMPLE_BRACKET). */ + if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0 + && (cset->ncoll_syms || cset->nranges)) + { + const int32_t *table = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + for (i = 0; i < SBC_MAX; ++i) + if (table[i] < 0) + re_set_fastmap (fastmap, icase, i); + } +# endif /* _LIBC */ + + /* See if we have to start the match at all multibyte characters, + i.e. where we would not find an invalid sequence. This only + applies to multibyte character sets; for single byte character + sets, the SIMPLE_BRACKET again suffices. */ + if (dfa->mb_cur_max > 1 + && (cset->nchar_classes || cset->non_match || cset->nranges +# ifdef _LIBC + || cset->nequiv_classes +# endif /* _LIBC */ + )) + { + unsigned char c = 0; + do + { + mbstate_t mbs; + memset (&mbs, 0, sizeof (mbs)); + if (__mbrtowc (NULL, (char *) &c, 1, &mbs) == (size_t) -2) + re_set_fastmap (fastmap, false, (int) c); + } + while (++c != 0); + } + + else + { + /* ... Else catch all bytes which can start the mbchars. */ + for (i = 0; i < cset->nmbchars; ++i) + { + char buf[256]; + mbstate_t state; + memset (&state, '\0', sizeof (state)); + if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1) + re_set_fastmap (fastmap, icase, *(unsigned char *) buf); + if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1) + { + if (__wcrtomb (buf, towlower (cset->mbchars[i]), &state) + != (size_t) -1) + re_set_fastmap (fastmap, false, *(unsigned char *) buf); + } + } + } + } +#endif /* RE_ENABLE_I18N */ + else if (type == OP_PERIOD +#ifdef RE_ENABLE_I18N + || type == OP_UTF8_PERIOD +#endif /* RE_ENABLE_I18N */ + || type == END_OF_RE) + { + memset (fastmap, '\1', sizeof (char) * SBC_MAX); + if (type == END_OF_RE) + bufp->can_be_null = 1; + return; + } + } +} + +/* Entry point for POSIX code. */ +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' to an allocated space for the fastmap; + `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +int +regcomp (regex_t *__restrict preg, + const char *__restrict pattern, + int cflags) +{ + reg_errcode_t ret; + reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED + : RE_SYNTAX_POSIX_BASIC); + + preg->buffer = NULL; + preg->allocated = 0; + preg->used = 0; + + /* Try to allocate space for the fastmap. */ + preg->fastmap = re_malloc (char, SBC_MAX); + if (BE (preg->fastmap == NULL, 0)) + return REG_ESPACE; + + syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + preg->no_sub = !!(cflags & REG_NOSUB); + preg->translate = NULL; + + ret = re_compile_internal (preg, pattern, strlen (pattern), syntax); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) + ret = REG_EPAREN; + + /* We have already checked preg->fastmap != NULL. */ + if (BE (ret == REG_NOERROR, 1)) + /* Compute the fastmap now, since regexec cannot modify the pattern + buffer. This function never fails in this implementation. */ + (void) re_compile_fastmap (preg); + else + { + /* Some error occurred while compiling the expression. */ + re_free (preg->fastmap); + preg->fastmap = NULL; + } + + return (int) ret; +} +#ifdef _LIBC +weak_alias (__regcomp, regcomp) +#endif + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. We don't use PREG here. */ + +size_t +regerror(int errcode, const regex_t *__restrict preg, + char *__restrict errbuf, size_t errbuf_size) +{ + const char *msg; + size_t msg_size; + + if (BE (errcode < 0 + || errcode >= (int) (sizeof (__re_error_msgid_idx) + / sizeof (__re_error_msgid_idx[0])), 0)) + /* Only error codes returned by the rest of the code should be passed + to this routine. If we are given anything else, or if other regex + code generates an invalid error code, then the program has a bug. + Dump core so we can fix it. */ + abort (); + + msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]); + + msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (BE (errbuf_size != 0, 1)) + { + if (BE (msg_size > errbuf_size, 0)) + { + memcpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; + } + else + memcpy (errbuf, msg, msg_size); + } + + return msg_size; +} +#ifdef _LIBC +weak_alias (__regerror, regerror) +#endif + + +#ifdef RE_ENABLE_I18N +/* This static array is used for the map to single-byte characters when + UTF-8 is used. Otherwise we would allocate memory just to initialize + it the same all the time. UTF-8 is the preferred encoding so this is + a worthwhile optimization. */ +#if __GNUC__ >= 3 +static const bitset_t utf8_sb_map = { + /* Set the first 128 bits. */ + [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX +}; +#else /* ! (__GNUC__ >= 3) */ +static bitset_t utf8_sb_map; +#endif /* __GNUC__ >= 3 */ +#endif /* RE_ENABLE_I18N */ + + +static void +free_dfa_content (re_dfa_t *dfa) +{ + unsigned int i; + int j; + + if (dfa->nodes) + for (i = 0; i < dfa->nodes_len; ++i) + free_token (dfa->nodes + i); + re_free (dfa->nexts); + for (i = 0; i < dfa->nodes_len; ++i) + { + if (dfa->eclosures != NULL) + re_node_set_free (dfa->eclosures + i); + if (dfa->inveclosures != NULL) + re_node_set_free (dfa->inveclosures + i); + if (dfa->edests != NULL) + re_node_set_free (dfa->edests + i); + } + re_free (dfa->edests); + re_free (dfa->eclosures); + re_free (dfa->inveclosures); + re_free (dfa->nodes); + + if (dfa->state_table) + for (i = 0; i <= dfa->state_hash_mask; ++i) + { + struct re_state_table_entry *entry = dfa->state_table + i; + for (j = 0; j < entry->num; ++j) + { + re_dfastate_t *state = entry->array[j]; + free_state (state); + } + re_free (entry->array); + } + re_free (dfa->state_table); +#ifdef RE_ENABLE_I18N + if (dfa->sb_char != utf8_sb_map) + re_free (dfa->sb_char); +#endif + re_free (dfa->subexp_map); +#ifdef DEBUG + re_free (dfa->re_str); +#endif + + re_free (dfa); +} + + +/* Free dynamically allocated space used by PREG. */ + +void +regfree (regex_t *preg) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + if (BE (dfa != NULL, 1)) + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + + re_free (preg->fastmap); + preg->fastmap = NULL; + + re_free (preg->translate); + preg->translate = NULL; +} +#ifdef _LIBC +weak_alias (__regfree, regfree) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC + +/* BSD has one and only one pattern buffer. */ +static struct re_pattern_buffer re_comp_buf; + +char * +# ifdef _LIBC +/* Make these definitions weak in libc, so POSIX programs can redefine + these names if they don't use our functions, and still use + regcomp/regexec above without link errors. */ +weak_function +# endif +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + char *fastmap; + + if (!s) + { + if (!re_comp_buf.buffer) + return gettext ("No previous regular expression"); + return 0; + } + + if (re_comp_buf.buffer) + { + fastmap = re_comp_buf.fastmap; + re_comp_buf.fastmap = NULL; + __regfree (&re_comp_buf); + memset (&re_comp_buf, '\0', sizeof (re_comp_buf)); + re_comp_buf.fastmap = fastmap; + } + + if (re_comp_buf.fastmap == NULL) + { + re_comp_buf.fastmap = (char *) malloc (SBC_MAX); + if (re_comp_buf.fastmap == NULL) + return (char *) gettext (__re_error_msgid + + __re_error_msgid_idx[(int) REG_ESPACE]); + } + + /* Since `re_exec' always passes NULL for the `regs' argument, we + don't need to initialize the pattern buffer fields which affect it. */ + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options); + + if (!ret) + return NULL; + + /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ + return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]); +} + +#ifdef _LIBC +libc_freeres_fn (free_mem) +{ + __regfree (&re_comp_buf); +} +#endif + +#endif /* _REGEX_RE_COMP */ + +/* Internal entry point. + Compile the regular expression PATTERN, whose length is LENGTH. + SYNTAX indicate regular expression's syntax. */ + +static reg_errcode_t +re_compile_internal (regex_t *preg, const char * pattern, size_t length, + reg_syntax_t syntax) +{ + reg_errcode_t err = REG_NOERROR; + re_dfa_t *dfa; + re_string_t regexp; + + /* Initialize the pattern buffer. */ + preg->fastmap_accurate = 0; + preg->syntax = syntax; + preg->not_bol = preg->not_eol = 0; + preg->used = 0; + preg->re_nsub = 0; + preg->can_be_null = 0; + preg->regs_allocated = REGS_UNALLOCATED; + + /* Initialize the dfa. */ + dfa = (re_dfa_t *) preg->buffer; + if (BE (preg->allocated < sizeof (re_dfa_t), 0)) + { + /* If zero allocated, but buffer is non-null, try to realloc + enough space. This loses if buffer's address is bogus, but + that is the user's responsibility. If ->buffer is NULL this + is a simple allocation. */ + dfa = re_realloc (preg->buffer, re_dfa_t, 1); + if (dfa == NULL) + return REG_ESPACE; + preg->allocated = sizeof (re_dfa_t); + preg->buffer = (unsigned char *) dfa; + } + preg->used = sizeof (re_dfa_t); + + err = init_dfa (dfa, length); + if (BE (err != REG_NOERROR, 0)) + { + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + return err; + } +#ifdef DEBUG + /* Note: length+1 will not overflow since it is checked in init_dfa. */ + dfa->re_str = re_malloc (char, length + 1); + strncpy (dfa->re_str, pattern, length + 1); +#endif + + __libc_lock_init (dfa->lock); + + err = re_string_construct (®exp, pattern, length, preg->translate, + syntax & RE_ICASE, dfa); + if (BE (err != REG_NOERROR, 0)) + { + re_compile_internal_free_return: + free_workarea_compile (preg); + re_string_destruct (®exp); + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + return err; + } + + /* Parse the regular expression, and build a structure tree. */ + preg->re_nsub = 0; + dfa->str_tree = parse (®exp, preg, syntax, &err); + if (BE (dfa->str_tree == NULL, 0)) + goto re_compile_internal_free_return; + + /* Analyze the tree and create the nfa. */ + err = analyze (preg); + if (BE (err != REG_NOERROR, 0)) + goto re_compile_internal_free_return; + +#ifdef RE_ENABLE_I18N + /* If possible, do searching in single byte encoding to speed things up. */ + if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL) + optimize_utf8 (dfa); +#endif + + /* Then create the initial state of the dfa. */ + err = create_initial_state (dfa); + + /* Release work areas. */ + free_workarea_compile (preg); + re_string_destruct (®exp); + + if (BE (err != REG_NOERROR, 0)) + { + free_dfa_content (dfa); + preg->buffer = NULL; + preg->allocated = 0; + } + + return err; +} + +/* Initialize DFA. We use the length of the regular expression PAT_LEN + as the initial length of some arrays. */ + +static reg_errcode_t +init_dfa (re_dfa_t *dfa, size_t pat_len) +{ + unsigned int table_size; + + memset (dfa, '\0', sizeof (re_dfa_t)); + + /* Force allocation of str_tree_storage the first time. */ + dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; + + /* Avoid overflows. */ + if (pat_len == SIZE_MAX) + return REG_ESPACE; + + dfa->nodes_alloc = pat_len + 1; + dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc); + + /* table_size = 2 ^ ceil(log pat_len) */ + for (table_size = 1; ; table_size <<= 1) + if (table_size > pat_len) + break; + + dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); + dfa->state_hash_mask = table_size - 1; + + dfa->mb_cur_max = MB_CUR_MAX; +#ifdef _LIBC + if (dfa->mb_cur_max == 6 + && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0) + dfa->is_utf8 = 1; + dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII) + != 0); +#else + dfa->is_utf8 = 1; + /* We check exhaustively in the loop below if this charset is a + superset of ASCII. */ + dfa->map_notascii = 0; +#endif + +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + if (dfa->is_utf8) + { +#if !defined(__GNUC__) || __GNUC__ < 3 + static short utf8_sb_map_inited = 0; + + if (! utf8_sb_map_inited) + { + int i; + + utf8_sb_map_inited = 0; + for (i = 0; i <= 0x80 / BITSET_WORD_BITS - 1; i++) + utf8_sb_map[i] = BITSET_WORD_MAX; + } +#endif + dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map; + } + else + { + int i, j, ch; + + dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); + if (BE (dfa->sb_char == NULL, 0)) + return REG_ESPACE; + + /* Set the bits corresponding to single byte chars. */ + for (i = 0, ch = 0; i < BITSET_WORDS; ++i) + for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) + { + wint_t wch = __btowc (ch); + if (wch != WEOF) + dfa->sb_char[i] |= (bitset_word_t) 1 << j; +# ifndef _LIBC + if (isascii (ch) && wch != ch) + dfa->map_notascii = 1; +# endif + } + } + } +#endif + + if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0)) + return REG_ESPACE; + return REG_NOERROR; +} + +/* Initialize WORD_CHAR table, which indicate which character is + "word". In this case "word" means that it is the word construction + character used by some operators like "\<", "\>", etc. */ + +static void +internal_function +init_word_char (re_dfa_t *dfa) +{ + int i, j, ch; + dfa->word_ops_used = 1; + for (i = 0, ch = 0; i < BITSET_WORDS; ++i) + for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch) + if (isalnum (ch) || ch == '_') + dfa->word_char[i] |= (bitset_word_t) 1 << j; +} + +/* Free the work area which are only used while compiling. */ + +static void +free_workarea_compile (regex_t *preg) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_storage_t *storage, *next; + for (storage = dfa->str_tree_storage; storage; storage = next) + { + next = storage->next; + re_free (storage); + } + dfa->str_tree_storage = NULL; + dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE; + dfa->str_tree = NULL; + re_free (dfa->org_indices); + dfa->org_indices = NULL; +} + +/* Create initial states for all contexts. */ + +static reg_errcode_t +create_initial_state (re_dfa_t *dfa) +{ + int first, i; + reg_errcode_t err; + re_node_set init_nodes; + + /* Initial states have the epsilon closure of the node which is + the first node of the regular expression. */ + first = dfa->str_tree->first->node_idx; + dfa->init_node = first; + err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* The back-references which are in initial states can epsilon transit, + since in this case all of the subexpressions can be null. + Then we add epsilon closures of the nodes which are the next nodes of + the back-references. */ + if (dfa->nbackref > 0) + for (i = 0; i < init_nodes.nelem; ++i) + { + int node_idx = init_nodes.elems[i]; + re_token_type_t type = dfa->nodes[node_idx].type; + + int clexp_idx; + if (type != OP_BACK_REF) + continue; + for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx) + { + re_token_t *clexp_node; + clexp_node = dfa->nodes + init_nodes.elems[clexp_idx]; + if (clexp_node->type == OP_CLOSE_SUBEXP + && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx) + break; + } + if (clexp_idx == init_nodes.nelem) + continue; + + if (type == OP_BACK_REF) + { + int dest_idx = dfa->edests[node_idx].elems[0]; + if (!re_node_set_contains (&init_nodes, dest_idx)) + { + reg_errcode_t err = re_node_set_merge (&init_nodes, + dfa->eclosures + + dest_idx); + if (err != REG_NOERROR) + return err; + i = 0; + } + } + } + + /* It must be the first time to invoke acquire_state. */ + dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0); + /* We don't check ERR here, since the initial state must not be NULL. */ + if (BE (dfa->init_state == NULL, 0)) + return err; + if (dfa->init_state->has_constraint) + { + dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes, + CONTEXT_WORD); + dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes, + CONTEXT_NEWLINE); + dfa->init_state_begbuf = re_acquire_state_context (&err, dfa, + &init_nodes, + CONTEXT_NEWLINE + | CONTEXT_BEGBUF); + if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL + || dfa->init_state_begbuf == NULL, 0)) + return err; + } + else + dfa->init_state_word = dfa->init_state_nl + = dfa->init_state_begbuf = dfa->init_state; + + re_node_set_free (&init_nodes); + return REG_NOERROR; +} + +#ifdef RE_ENABLE_I18N +/* If it is possible to do searching in single byte encoding instead of UTF-8 + to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change + DFA nodes where needed. */ + +static void +optimize_utf8 (re_dfa_t *dfa) +{ + int node, i, mb_chars = 0, has_period = 0; + + for (node = 0; node < dfa->nodes_len; ++node) + switch (dfa->nodes[node].type) + { + case CHARACTER: + if (dfa->nodes[node].opr.c >= 0x80) + mb_chars = 1; + break; + case ANCHOR: + switch (dfa->nodes[node].opr.ctx_type) + { + case LINE_FIRST: + case LINE_LAST: + case BUF_FIRST: + case BUF_LAST: + break; + default: + /* Word anchors etc. cannot be handled. It's okay to test + opr.ctx_type since constraints (for all DFA nodes) are + created by ORing one or more opr.ctx_type values. */ + return; + } + break; + case OP_PERIOD: + has_period = 1; + break; + case OP_BACK_REF: + case OP_ALT: + case END_OF_RE: + case OP_DUP_ASTERISK: + case OP_OPEN_SUBEXP: + case OP_CLOSE_SUBEXP: + break; + case COMPLEX_BRACKET: + return; + case SIMPLE_BRACKET: + /* Just double check. The non-ASCII range starts at 0x80. */ + assert (0x80 % BITSET_WORD_BITS == 0); + for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i) + if (dfa->nodes[node].opr.sbcset[i]) + return; + break; + default: + abort (); + } + + if (mb_chars || has_period) + for (node = 0; node < dfa->nodes_len; ++node) + { + if (dfa->nodes[node].type == CHARACTER + && dfa->nodes[node].opr.c >= 0x80) + dfa->nodes[node].mb_partial = 0; + else if (dfa->nodes[node].type == OP_PERIOD) + dfa->nodes[node].type = OP_UTF8_PERIOD; + } + + /* The search can be in single byte locale. */ + dfa->mb_cur_max = 1; + dfa->is_utf8 = 0; + dfa->has_mb_node = dfa->nbackref > 0 || has_period; +} +#endif + +/* Analyze the structure tree, and calculate "first", "next", "edest", + "eclosure", and "inveclosure". */ + +static reg_errcode_t +analyze (regex_t *preg) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + reg_errcode_t ret; + + /* Allocate arrays. */ + dfa->nexts = re_malloc (int, dfa->nodes_alloc); + dfa->org_indices = re_malloc (int, dfa->nodes_alloc); + dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc); + dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc); + if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL + || dfa->eclosures == NULL, 0)) + return REG_ESPACE; + + dfa->subexp_map = re_malloc (int, preg->re_nsub); + if (dfa->subexp_map != NULL) + { + unsigned int i; + for (i = 0; i < preg->re_nsub; i++) + dfa->subexp_map[i] = i; + preorder (dfa->str_tree, optimize_subexps, dfa); + for (i = 0; i < preg->re_nsub; i++) + if (dfa->subexp_map[i] != i) + break; + if (i == preg->re_nsub) + { + free (dfa->subexp_map); + dfa->subexp_map = NULL; + } + } + + ret = postorder (dfa->str_tree, lower_subexps, preg); + if (BE (ret != REG_NOERROR, 0)) + return ret; + ret = postorder (dfa->str_tree, calc_first, dfa); + if (BE (ret != REG_NOERROR, 0)) + return ret; + preorder (dfa->str_tree, calc_next, dfa); + ret = preorder (dfa->str_tree, link_nfa_nodes, dfa); + if (BE (ret != REG_NOERROR, 0)) + return ret; + ret = calc_eclosure (dfa); + if (BE (ret != REG_NOERROR, 0)) + return ret; + + /* We only need this during the prune_impossible_nodes pass in regexec.c; + skip it if p_i_n will not run, as calc_inveclosure can be quadratic. */ + if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match) + || dfa->nbackref) + { + dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len); + if (BE (dfa->inveclosures == NULL, 0)) + return REG_ESPACE; + ret = calc_inveclosure (dfa); + } + + return ret; +} + +/* Our parse trees are very unbalanced, so we cannot use a stack to + implement parse tree visits. Instead, we use parent pointers and + some hairy code in these two functions. */ +static reg_errcode_t +postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), + void *extra) +{ + bin_tree_t *node, *prev; + + for (node = root; ; ) + { + /* Descend down the tree, preferably to the left (or to the right + if that's the only child). */ + while (node->left || node->right) + if (node->left) + node = node->left; + else + node = node->right; + + do + { + reg_errcode_t err = fn (extra, node); + if (BE (err != REG_NOERROR, 0)) + return err; + if (node->parent == NULL) + return REG_NOERROR; + prev = node; + node = node->parent; + } + /* Go up while we have a node that is reached from the right. */ + while (node->right == prev || node->right == NULL); + node = node->right; + } +} + +static reg_errcode_t +preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)), + void *extra) +{ + bin_tree_t *node; + + for (node = root; ; ) + { + reg_errcode_t err = fn (extra, node); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* Go to the left node, or up and to the right. */ + if (node->left) + node = node->left; + else + { + bin_tree_t *prev = NULL; + while (node->right == prev || node->right == NULL) + { + prev = node; + node = node->parent; + if (!node) + return REG_NOERROR; + } + node = node->right; + } + } +} + +/* Optimization pass: if a SUBEXP is entirely contained, strip it and tell + re_search_internal to map the inner one's opr.idx to this one's. Adjust + backreferences as well. Requires a preorder visit. */ +static reg_errcode_t +optimize_subexps (void *extra, bin_tree_t *node) +{ + re_dfa_t *dfa = (re_dfa_t *) extra; + + if (node->token.type == OP_BACK_REF && dfa->subexp_map) + { + int idx = node->token.opr.idx; + node->token.opr.idx = dfa->subexp_map[idx]; + dfa->used_bkref_map |= 1 << node->token.opr.idx; + } + + else if (node->token.type == SUBEXP + && node->left && node->left->token.type == SUBEXP) + { + int other_idx = node->left->token.opr.idx; + + node->left = node->left->left; + if (node->left) + node->left->parent = node; + + dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx]; + if (other_idx < BITSET_WORD_BITS) + dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx); + } + + return REG_NOERROR; +} + +/* Lowering pass: Turn each SUBEXP node into the appropriate concatenation + of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP. */ +static reg_errcode_t +lower_subexps (void *extra, bin_tree_t *node) +{ + regex_t *preg = (regex_t *) extra; + reg_errcode_t err = REG_NOERROR; + + if (node->left && node->left->token.type == SUBEXP) + { + node->left = lower_subexp (&err, preg, node->left); + if (node->left) + node->left->parent = node; + } + if (node->right && node->right->token.type == SUBEXP) + { + node->right = lower_subexp (&err, preg, node->right); + if (node->right) + node->right->parent = node; + } + + return err; +} + +static bin_tree_t * +lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *body = node->left; + bin_tree_t *op, *cls, *tree1, *tree; + + if (preg->no_sub + /* We do not optimize empty subexpressions, because otherwise we may + have bad CONCAT nodes with NULL children. This is obviously not + very common, so we do not lose much. An example that triggers + this case is the sed "script" /\(\)/x. */ + && node->left != NULL + && (node->token.opr.idx >= BITSET_WORD_BITS + || !(dfa->used_bkref_map + & ((bitset_word_t) 1 << node->token.opr.idx)))) + return node->left; + + /* Convert the SUBEXP node to the concatenation of an + OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */ + op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP); + cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP); + tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls; + tree = create_tree (dfa, op, tree1, CONCAT); + if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + + op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx; + op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp; + return tree; +} + +/* Pass 1 in building the NFA: compute FIRST and create unlinked automaton + nodes. Requires a postorder visit. */ +static reg_errcode_t +calc_first (void *extra, bin_tree_t *node) +{ + re_dfa_t *dfa = (re_dfa_t *) extra; + if (node->token.type == CONCAT) + { + node->first = node->left->first; + node->node_idx = node->left->node_idx; + } + else + { + node->first = node; + node->node_idx = re_dfa_add_node (dfa, node->token); + if (BE (node->node_idx == -1, 0)) + return REG_ESPACE; + if (node->token.type == ANCHOR) + dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type; + } + return REG_NOERROR; +} + +/* Pass 2: compute NEXT on the tree. Preorder visit. */ +static reg_errcode_t +calc_next (void *extra, bin_tree_t *node) +{ + switch (node->token.type) + { + case OP_DUP_ASTERISK: + node->left->next = node; + break; + case CONCAT: + node->left->next = node->right->first; + node->right->next = node->next; + break; + default: + if (node->left) + node->left->next = node->next; + if (node->right) + node->right->next = node->next; + break; + } + return REG_NOERROR; +} + +/* Pass 3: link all DFA nodes to their NEXT node (any order will do). */ +static reg_errcode_t +link_nfa_nodes (void *extra, bin_tree_t *node) +{ + re_dfa_t *dfa = (re_dfa_t *) extra; + int idx = node->node_idx; + reg_errcode_t err = REG_NOERROR; + + switch (node->token.type) + { + case CONCAT: + break; + + case END_OF_RE: + assert (node->next == NULL); + break; + + case OP_DUP_ASTERISK: + case OP_ALT: + { + int left, right; + dfa->has_plural_match = 1; + if (node->left != NULL) + left = node->left->first->node_idx; + else + left = node->next->node_idx; + if (node->right != NULL) + right = node->right->first->node_idx; + else + right = node->next->node_idx; + assert (left > -1); + assert (right > -1); + err = re_node_set_init_2 (dfa->edests + idx, left, right); + } + break; + + case ANCHOR: + case OP_OPEN_SUBEXP: + case OP_CLOSE_SUBEXP: + err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx); + break; + + case OP_BACK_REF: + dfa->nexts[idx] = node->next->node_idx; + if (node->token.type == OP_BACK_REF) + err = re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]); + break; + + default: + assert (!IS_EPSILON_NODE (node->token.type)); + dfa->nexts[idx] = node->next->node_idx; + break; + } + + return err; +} + +/* Duplicate the epsilon closure of the node ROOT_NODE. + Note that duplicated nodes have constraint INIT_CONSTRAINT in addition + to their own constraint. */ + +static reg_errcode_t +internal_function +duplicate_node_closure (re_dfa_t *dfa, int top_org_node, int top_clone_node, + int root_node, unsigned int init_constraint) +{ + int org_node, clone_node, ret; + unsigned int constraint = init_constraint; + for (org_node = top_org_node, clone_node = top_clone_node;;) + { + int org_dest, clone_dest; + if (dfa->nodes[org_node].type == OP_BACK_REF) + { + /* If the back reference epsilon-transit, its destination must + also have the constraint. Then duplicate the epsilon closure + of the destination of the back reference, and store it in + edests of the back reference. */ + org_dest = dfa->nexts[org_node]; + re_node_set_empty (dfa->edests + clone_node); + clone_dest = duplicate_node (dfa, org_dest, constraint); + if (BE (clone_dest == -1, 0)) + return REG_ESPACE; + dfa->nexts[clone_node] = dfa->nexts[org_node]; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + else if (dfa->edests[org_node].nelem == 0) + { + /* In case of the node can't epsilon-transit, don't duplicate the + destination and store the original destination as the + destination of the node. */ + dfa->nexts[clone_node] = dfa->nexts[org_node]; + break; + } + else if (dfa->edests[org_node].nelem == 1) + { + /* In case of the node can epsilon-transit, and it has only one + destination. */ + org_dest = dfa->edests[org_node].elems[0]; + re_node_set_empty (dfa->edests + clone_node); + /* If the node is root_node itself, it means the epsilon clsoure + has a loop. Then tie it to the destination of the root_node. */ + if (org_node == root_node && clone_node != org_node) + { + ret = re_node_set_insert (dfa->edests + clone_node, org_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + break; + } + /* In case of the node has another constraint, add it. */ + constraint |= dfa->nodes[org_node].constraint; + clone_dest = duplicate_node (dfa, org_dest, constraint); + if (BE (clone_dest == -1, 0)) + return REG_ESPACE; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + else /* dfa->edests[org_node].nelem == 2 */ + { + /* In case of the node can epsilon-transit, and it has two + destinations. In the bin_tree_t and DFA, that's '|' and '*'. */ + org_dest = dfa->edests[org_node].elems[0]; + re_node_set_empty (dfa->edests + clone_node); + /* Search for a duplicated node which satisfies the constraint. */ + clone_dest = search_duplicated_node (dfa, org_dest, constraint); + if (clone_dest == -1) + { + /* There is no such duplicated node, create a new one. */ + reg_errcode_t err; + clone_dest = duplicate_node (dfa, org_dest, constraint); + if (BE (clone_dest == -1, 0)) + return REG_ESPACE; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + err = duplicate_node_closure (dfa, org_dest, clone_dest, + root_node, constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + } + else + { + /* There is a duplicated node which satisfies the constraint, + use it to avoid infinite loop. */ + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + + org_dest = dfa->edests[org_node].elems[1]; + clone_dest = duplicate_node (dfa, org_dest, constraint); + if (BE (clone_dest == -1, 0)) + return REG_ESPACE; + ret = re_node_set_insert (dfa->edests + clone_node, clone_dest); + if (BE (ret < 0, 0)) + return REG_ESPACE; + } + org_node = org_dest; + clone_node = clone_dest; + } + return REG_NOERROR; +} + +/* Search for a node which is duplicated from the node ORG_NODE, and + satisfies the constraint CONSTRAINT. */ + +static int +search_duplicated_node (const re_dfa_t *dfa, int org_node, + unsigned int constraint) +{ + int idx; + for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx) + { + if (org_node == dfa->org_indices[idx] + && constraint == dfa->nodes[idx].constraint) + return idx; /* Found. */ + } + return -1; /* Not found. */ +} + +/* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT. + Return the index of the new node, or -1 if insufficient storage is + available. */ + +static int +duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint) +{ + int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]); + if (BE (dup_idx != -1, 1)) + { + dfa->nodes[dup_idx].constraint = constraint; + dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint; + dfa->nodes[dup_idx].duplicated = 1; + + /* Store the index of the original node. */ + dfa->org_indices[dup_idx] = org_idx; + } + return dup_idx; +} + +static reg_errcode_t +calc_inveclosure (re_dfa_t *dfa) +{ + int ret; + unsigned int src, idx; + for (idx = 0; idx < dfa->nodes_len; ++idx) + re_node_set_init_empty (dfa->inveclosures + idx); + + for (src = 0; src < dfa->nodes_len; ++src) + { + int *elems = dfa->eclosures[src].elems; + int idx; + for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx) + { + ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src); + if (BE (ret == -1, 0)) + return REG_ESPACE; + } + } + + return REG_NOERROR; +} + +/* Calculate "eclosure" for all the node in DFA. */ + +static reg_errcode_t +calc_eclosure (re_dfa_t *dfa) +{ + int node_idx, incomplete; +#ifdef DEBUG + assert (dfa->nodes_len > 0); +#endif + incomplete = 0; + /* For each nodes, calculate epsilon closure. */ + for (node_idx = 0; ; ++node_idx) + { + reg_errcode_t err; + re_node_set eclosure_elem; + if (node_idx == dfa->nodes_len) + { + if (!incomplete) + break; + incomplete = 0; + node_idx = 0; + } + +#ifdef DEBUG + assert (dfa->eclosures[node_idx].nelem != -1); +#endif + + /* If we have already calculated, skip it. */ + if (dfa->eclosures[node_idx].nelem != 0) + continue; + /* Calculate epsilon closure of `node_idx'. */ + err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (dfa->eclosures[node_idx].nelem == 0) + { + incomplete = 1; + re_node_set_free (&eclosure_elem); + } + } + return REG_NOERROR; +} + +/* Calculate epsilon closure of NODE. */ + +static reg_errcode_t +calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root) +{ + reg_errcode_t err; + int i; + re_node_set eclosure; + int ret; + int incomplete = 0; + err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* This indicates that we are calculating this node now. + We reference this value to avoid infinite loop. */ + dfa->eclosures[node].nelem = -1; + + /* If the current node has constraints, duplicate all nodes + since they must inherit the constraints. */ + if (dfa->nodes[node].constraint + && dfa->edests[node].nelem + && !dfa->nodes[dfa->edests[node].elems[0]].duplicated) + { + err = duplicate_node_closure (dfa, node, node, node, + dfa->nodes[node].constraint); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + /* Expand each epsilon destination nodes. */ + if (IS_EPSILON_NODE(dfa->nodes[node].type)) + for (i = 0; i < dfa->edests[node].nelem; ++i) + { + re_node_set eclosure_elem; + int edest = dfa->edests[node].elems[i]; + /* If calculating the epsilon closure of `edest' is in progress, + return intermediate result. */ + if (dfa->eclosures[edest].nelem == -1) + { + incomplete = 1; + continue; + } + /* If we haven't calculated the epsilon closure of `edest' yet, + calculate now. Otherwise use calculated epsilon closure. */ + if (dfa->eclosures[edest].nelem == 0) + { + err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0); + if (BE (err != REG_NOERROR, 0)) + return err; + } + else + eclosure_elem = dfa->eclosures[edest]; + /* Merge the epsilon closure of `edest'. */ + err = re_node_set_merge (&eclosure, &eclosure_elem); + if (BE (err != REG_NOERROR, 0)) + return err; + /* If the epsilon closure of `edest' is incomplete, + the epsilon closure of this node is also incomplete. */ + if (dfa->eclosures[edest].nelem == 0) + { + incomplete = 1; + re_node_set_free (&eclosure_elem); + } + } + + /* An epsilon closure includes itself. */ + ret = re_node_set_insert (&eclosure, node); + if (BE (ret < 0, 0)) + return REG_ESPACE; + if (incomplete && !root) + dfa->eclosures[node].nelem = 0; + else + dfa->eclosures[node] = eclosure; + *new_set = eclosure; + return REG_NOERROR; +} + +/* Functions for token which are used in the parser. */ + +/* Fetch a token from INPUT. + We must not use this function inside bracket expressions. */ + +static void +internal_function +fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax) +{ + re_string_skip_bytes (input, peek_token (result, input, syntax)); +} + +/* Peek a token from INPUT, and return the length of the token. + We must not use this function inside bracket expressions. */ + +static int +internal_function +peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax) +{ + unsigned char c; + + if (re_string_eoi (input)) + { + token->type = END_OF_RE; + return 0; + } + + c = re_string_peek_byte (input, 0); + token->opr.c = c; + + token->word_char = 0; +#ifdef RE_ENABLE_I18N + token->mb_partial = 0; + if (input->mb_cur_max > 1 && + !re_string_first_byte (input, re_string_cur_idx (input))) + { + token->type = CHARACTER; + token->mb_partial = 1; + return 1; + } +#endif + if (c == '\\') + { + unsigned char c2; + if (re_string_cur_idx (input) + 1 >= re_string_length (input)) + { + token->type = BACK_SLASH; + return 1; + } + + c2 = re_string_peek_byte_case (input, 1); + token->opr.c = c2; + token->type = CHARACTER; +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc = re_string_wchar_at (input, + re_string_cur_idx (input) + 1); + token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; + } + else +#endif + token->word_char = IS_WORD_CHAR (c2) != 0; + + switch (c2) + { + case '|': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR)) + token->type = OP_ALT; + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (!(syntax & RE_NO_BK_REFS)) + { + token->type = OP_BACK_REF; + token->opr.idx = c2 - '1'; + } + break; + case '<': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_FIRST; + } + break; + case '>': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_LAST; + } + break; + case 'b': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = WORD_DELIM; + } + break; + case 'B': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = NOT_WORD_DELIM; + } + break; + case 'w': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_WORD; + break; + case 'W': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_NOTWORD; + break; + case 's': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_SPACE; + break; + case 'S': + if (!(syntax & RE_NO_GNU_OPS)) + token->type = OP_NOTSPACE; + break; + case '`': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = BUF_FIRST; + } + break; + case '\'': + if (!(syntax & RE_NO_GNU_OPS)) + { + token->type = ANCHOR; + token->opr.ctx_type = BUF_LAST; + } + break; + case '(': + if (!(syntax & RE_NO_BK_PARENS)) + token->type = OP_OPEN_SUBEXP; + break; + case ')': + if (!(syntax & RE_NO_BK_PARENS)) + token->type = OP_CLOSE_SUBEXP; + break; + case '+': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_PLUS; + break; + case '?': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_QUESTION; + break; + case '{': + if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) + token->type = OP_OPEN_DUP_NUM; + break; + case '}': + if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES))) + token->type = OP_CLOSE_DUP_NUM; + break; + default: + break; + } + return 2; + } + + token->type = CHARACTER; +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input)); + token->word_char = IS_WIDE_WORD_CHAR (wc) != 0; + } + else +#endif + token->word_char = IS_WORD_CHAR (token->opr.c); + + switch (c) + { + case '\n': + if (syntax & RE_NEWLINE_ALT) + token->type = OP_ALT; + break; + case '|': + if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR)) + token->type = OP_ALT; + break; + case '*': + token->type = OP_DUP_ASTERISK; + break; + case '+': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_PLUS; + break; + case '?': + if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM)) + token->type = OP_DUP_QUESTION; + break; + case '{': + if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + token->type = OP_OPEN_DUP_NUM; + break; + case '}': + if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + token->type = OP_CLOSE_DUP_NUM; + break; + case '(': + if (syntax & RE_NO_BK_PARENS) + token->type = OP_OPEN_SUBEXP; + break; + case ')': + if (syntax & RE_NO_BK_PARENS) + token->type = OP_CLOSE_SUBEXP; + break; + case '[': + token->type = OP_OPEN_BRACKET; + break; + case '.': + token->type = OP_PERIOD; + break; + case '^': + if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) && + re_string_cur_idx (input) != 0) + { + char prev = re_string_peek_byte (input, -1); + if (!(syntax & RE_NEWLINE_ALT) || prev != '\n') + break; + } + token->type = ANCHOR; + token->opr.ctx_type = LINE_FIRST; + break; + case '$': + if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) && + re_string_cur_idx (input) + 1 != re_string_length (input)) + { + re_token_t next; + re_string_skip_bytes (input, 1); + peek_token (&next, input, syntax); + re_string_skip_bytes (input, -1); + if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP) + break; + } + token->type = ANCHOR; + token->opr.ctx_type = LINE_LAST; + break; + default: + break; + } + return 1; +} + +/* Peek a token from INPUT, and return the length of the token. + We must not use this function out of bracket expressions. */ + +static int +internal_function +peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax) +{ + unsigned char c; + if (re_string_eoi (input)) + { + token->type = END_OF_RE; + return 0; + } + c = re_string_peek_byte (input, 0); + token->opr.c = c; + +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1 && + !re_string_first_byte (input, re_string_cur_idx (input))) + { + token->type = CHARACTER; + return 1; + } +#endif /* RE_ENABLE_I18N */ + + if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) + && re_string_cur_idx (input) + 1 < re_string_length (input)) + { + /* In this case, '\' escape a character. */ + unsigned char c2; + re_string_skip_bytes (input, 1); + c2 = re_string_peek_byte (input, 0); + token->opr.c = c2; + token->type = CHARACTER; + return 1; + } + if (c == '[') /* '[' is a special char in a bracket exps. */ + { + unsigned char c2; + int token_len; + if (re_string_cur_idx (input) + 1 < re_string_length (input)) + c2 = re_string_peek_byte (input, 1); + else + c2 = 0; + token->opr.c = c2; + token_len = 2; + switch (c2) + { + case '.': + token->type = OP_OPEN_COLL_ELEM; + break; + case '=': + token->type = OP_OPEN_EQUIV_CLASS; + break; + case ':': + if (syntax & RE_CHAR_CLASSES) + { + token->type = OP_OPEN_CHAR_CLASS; + break; + } + /* else fall through. */ + default: + token->type = CHARACTER; + token->opr.c = c; + token_len = 1; + break; + } + return token_len; + } + switch (c) + { + case '-': + token->type = OP_CHARSET_RANGE; + break; + case ']': + token->type = OP_CLOSE_BRACKET; + break; + case '^': + token->type = OP_NON_MATCH_LIST; + break; + default: + token->type = CHARACTER; + } + return 1; +} + +/* Functions for parser. */ + +/* Entry point of the parser. + Parse the regular expression REGEXP and return the structure tree. + If an error is occured, ERR is set by error code, and return NULL. + This function build the following tree, from regular expression : + CAT + / \ + / \ + EOR + + CAT means concatenation. + EOR means end of regular expression. */ + +static bin_tree_t * +parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax, + reg_errcode_t *err) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree, *eor, *root; + re_token_t current_token; + dfa->syntax = syntax; + fetch_token (¤t_token, regexp, syntax | RE_CARET_ANCHORS_HERE); + tree = parse_reg_exp (regexp, preg, ¤t_token, syntax, 0, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + eor = create_tree (dfa, NULL, NULL, END_OF_RE); + if (tree != NULL) + root = create_tree (dfa, tree, eor, CONCAT); + else + root = eor; + if (BE (eor == NULL || root == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + return root; +} + +/* This function build the following tree, from regular expression + |: + ALT + / \ + / \ + + + ALT means alternative, which represents the operator `|'. */ + +static bin_tree_t * +parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, + reg_syntax_t syntax, int nest, reg_errcode_t *err) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree, *branch = NULL; + tree = parse_branch (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + + while (token->type == OP_ALT) + { + fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); + if (token->type != OP_ALT && token->type != END_OF_RE + && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) + { + branch = parse_branch (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && branch == NULL, 0)) + return NULL; + } + else + branch = NULL; + tree = create_tree (dfa, tree, branch, OP_ALT); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + return tree; +} + +/* This function build the following tree, from regular expression + : + CAT + / \ + / \ + + + CAT means concatenation. */ + +static bin_tree_t * +parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token, + reg_syntax_t syntax, int nest, reg_errcode_t *err) +{ + bin_tree_t *tree, *exp; + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + tree = parse_expression (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + + while (token->type != OP_ALT && token->type != END_OF_RE + && (nest == 0 || token->type != OP_CLOSE_SUBEXP)) + { + exp = parse_expression (regexp, preg, token, syntax, nest, err); + if (BE (*err != REG_NOERROR && exp == NULL, 0)) + { + return NULL; + } + if (tree != NULL && exp != NULL) + { + tree = create_tree (dfa, tree, exp, CONCAT); + if (tree == NULL) + { + *err = REG_ESPACE; + return NULL; + } + } + else if (tree == NULL) + tree = exp; + /* Otherwise exp == NULL, we don't need to create new tree. */ + } + return tree; +} + +/* This function build the following tree, from regular expression a*: + * + | + a +*/ + +static bin_tree_t * +parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token, + reg_syntax_t syntax, int nest, reg_errcode_t *err) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree; + switch (token->type) + { + case CHARACTER: + tree = create_token_tree (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + while (!re_string_eoi (regexp) + && !re_string_first_byte (regexp, re_string_cur_idx (regexp))) + { + bin_tree_t *mbc_remain; + fetch_token (token, regexp, syntax); + mbc_remain = create_token_tree (dfa, NULL, NULL, token); + tree = create_tree (dfa, tree, mbc_remain, CONCAT); + if (BE (mbc_remain == NULL || tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + } +#endif + break; + case OP_OPEN_SUBEXP: + tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_OPEN_BRACKET: + tree = parse_bracket_exp (regexp, dfa, token, syntax, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_BACK_REF: + if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1)) + { + *err = REG_ESUBREG; + return NULL; + } + dfa->used_bkref_map |= 1 << token->opr.idx; + tree = create_token_tree (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + ++dfa->nbackref; + dfa->has_mb_node = 1; + break; + case OP_OPEN_DUP_NUM: + if (syntax & RE_CONTEXT_INVALID_DUP) + { + *err = REG_BADRPT; + return NULL; + } + /* FALLTHROUGH */ + case OP_DUP_ASTERISK: + case OP_DUP_PLUS: + case OP_DUP_QUESTION: + if (syntax & RE_CONTEXT_INVALID_OPS) + { + *err = REG_BADRPT; + return NULL; + } + else if (syntax & RE_CONTEXT_INDEP_OPS) + { + fetch_token (token, regexp, syntax); + return parse_expression (regexp, preg, token, syntax, nest, err); + } + /* else fall through */ + case OP_CLOSE_SUBEXP: + if ((token->type == OP_CLOSE_SUBEXP) && + !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)) + { + *err = REG_ERPAREN; + return NULL; + } + /* else fall through */ + case OP_CLOSE_DUP_NUM: + /* We treat it as a normal character. */ + + /* Then we can these characters as normal characters. */ + token->type = CHARACTER; + /* mb_partial and word_char bits should be initialized already + by peek_token. */ + tree = create_token_tree (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + break; + case ANCHOR: + if ((token->opr.ctx_type + & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST)) + && dfa->word_ops_used == 0) + init_word_char (dfa); + if (token->opr.ctx_type == WORD_DELIM + || token->opr.ctx_type == NOT_WORD_DELIM) + { + bin_tree_t *tree_first, *tree_last; + if (token->opr.ctx_type == WORD_DELIM) + { + token->opr.ctx_type = WORD_FIRST; + tree_first = create_token_tree (dfa, NULL, NULL, token); + token->opr.ctx_type = WORD_LAST; + } + else + { + token->opr.ctx_type = INSIDE_WORD; + tree_first = create_token_tree (dfa, NULL, NULL, token); + token->opr.ctx_type = INSIDE_NOTWORD; + } + tree_last = create_token_tree (dfa, NULL, NULL, token); + tree = create_tree (dfa, tree_first, tree_last, OP_ALT); + if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + else + { + tree = create_token_tree (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + } + /* We must return here, since ANCHORs can't be followed + by repetition operators. + eg. RE"^*" is invalid or "", + it must not be "". */ + fetch_token (token, regexp, syntax); + return tree; + case OP_PERIOD: + tree = create_token_tree (dfa, NULL, NULL, token); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + if (dfa->mb_cur_max > 1) + dfa->has_mb_node = 1; + break; + case OP_WORD: + case OP_NOTWORD: + tree = build_charclass_op (dfa, regexp->trans, + "alnum", + "_", + token->type == OP_NOTWORD, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_SPACE: + case OP_NOTSPACE: + tree = build_charclass_op (dfa, regexp->trans, + "space", + "", + token->type == OP_NOTSPACE, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + break; + case OP_ALT: + case END_OF_RE: + return NULL; + case BACK_SLASH: + *err = REG_EESCAPE; + return NULL; + default: + /* Must not happen? */ +#ifdef DEBUG + assert (0); +#endif + return NULL; + } + fetch_token (token, regexp, syntax); + + while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS + || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM) + { + tree = parse_dup_op (tree, regexp, dfa, token, syntax, err); + if (BE (*err != REG_NOERROR && tree == NULL, 0)) + return NULL; + /* In BRE consecutive duplications are not allowed. */ + if ((syntax & RE_CONTEXT_INVALID_DUP) + && (token->type == OP_DUP_ASTERISK + || token->type == OP_OPEN_DUP_NUM)) + { + *err = REG_BADRPT; + return NULL; + } + } + + return tree; +} + +/* This function build the following tree, from regular expression + (): + SUBEXP + | + +*/ + +static bin_tree_t * +parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token, + reg_syntax_t syntax, int nest, reg_errcode_t *err) +{ + re_dfa_t *dfa = (re_dfa_t *) preg->buffer; + bin_tree_t *tree; + size_t cur_nsub; + cur_nsub = preg->re_nsub++; + + fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE); + + /* The subexpression may be a null string. */ + if (token->type == OP_CLOSE_SUBEXP) + tree = NULL; + else + { + tree = parse_reg_exp (regexp, preg, token, syntax, nest, err); + if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0)) + *err = REG_EPAREN; + if (BE (*err != REG_NOERROR, 0)) + return NULL; + } + + if (cur_nsub <= '9' - '1') + dfa->completed_bkref_map |= 1 << cur_nsub; + + tree = create_tree (dfa, tree, NULL, SUBEXP); + if (BE (tree == NULL, 0)) + { + *err = REG_ESPACE; + return NULL; + } + tree->token.opr.idx = cur_nsub; + return tree; +} + +/* This function parse repetition operators like "*", "+", "{1,3}" etc. */ + +static bin_tree_t * +parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa, + re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err) +{ + bin_tree_t *tree = NULL, *old_tree = NULL; + int i, start, end, start_idx = re_string_cur_idx (regexp); +#ifndef RE_TOKEN_INIT_BUG + re_token_t start_token = *token; +#else + re_token_t start_token; + + memcpy ((void *) &start_token, (void *) token, sizeof start_token); +#endif + + if (token->type == OP_OPEN_DUP_NUM) + { + end = 0; + start = fetch_number (regexp, token, syntax); + if (start == -1) + { + if (token->type == CHARACTER && token->opr.c == ',') + start = 0; /* We treat "{,m}" as "{0,m}". */ + else + { + *err = REG_BADBR; /* {} is invalid. */ + return NULL; + } + } + if (BE (start != -2, 1)) + { + /* We treat "{n}" as "{n,n}". */ + end = ((token->type == OP_CLOSE_DUP_NUM) ? start + : ((token->type == CHARACTER && token->opr.c == ',') + ? fetch_number (regexp, token, syntax) : -2)); + } + if (BE (start == -2 || end == -2, 0)) + { + /* Invalid sequence. */ + if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0)) + { + if (token->type == END_OF_RE) + *err = REG_EBRACE; + else + *err = REG_BADBR; + + return NULL; + } + + /* If the syntax bit is set, rollback. */ + re_string_set_index (regexp, start_idx); + *token = start_token; + token->type = CHARACTER; + /* mb_partial and word_char bits should be already initialized by + peek_token. */ + return elem; + } + + if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0)) + { + /* First number greater than second. */ + *err = REG_BADBR; + return NULL; + } + } + else + { + start = (token->type == OP_DUP_PLUS) ? 1 : 0; + end = (token->type == OP_DUP_QUESTION) ? 1 : -1; + } + + fetch_token (token, regexp, syntax); + + if (BE (elem == NULL, 0)) + return NULL; + if (BE (start == 0 && end == 0, 0)) + { + postorder (elem, free_tree, NULL); + return NULL; + } + + /* Extract "{n,m}" to "...{0,}". */ + if (BE (start > 0, 0)) + { + tree = elem; + for (i = 2; i <= start; ++i) + { + elem = duplicate_tree (elem, dfa); + tree = create_tree (dfa, tree, elem, CONCAT); + if (BE (elem == NULL || tree == NULL, 0)) + goto parse_dup_op_espace; + } + + if (start == end) + return tree; + + /* Duplicate ELEM before it is marked optional. */ + elem = duplicate_tree (elem, dfa); + old_tree = tree; + } + else + old_tree = NULL; + + if (elem->token.type == SUBEXP) + postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx); + + tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT)); + if (BE (tree == NULL, 0)) + goto parse_dup_op_espace; + + /* This loop is actually executed only when end != -1, + to rewrite {0,n} as ((...?)?)?... We have + already created the start+1-th copy. */ + for (i = start + 2; i <= end; ++i) + { + elem = duplicate_tree (elem, dfa); + tree = create_tree (dfa, tree, elem, CONCAT); + if (BE (elem == NULL || tree == NULL, 0)) + goto parse_dup_op_espace; + + tree = create_tree (dfa, tree, NULL, OP_ALT); + if (BE (tree == NULL, 0)) + goto parse_dup_op_espace; + } + + if (old_tree) + tree = create_tree (dfa, old_tree, tree, CONCAT); + + return tree; + + parse_dup_op_espace: + *err = REG_ESPACE; + return NULL; +} + +/* Size of the names for collating symbol/equivalence_class/character_class. + I'm not sure, but maybe enough. */ +#define BRACKET_NAME_BUF_SIZE 32 + +#ifndef _LIBC + /* Local function for parse_bracket_exp only used in case of NOT _LIBC. + Build the range expression which starts from START_ELEM, and ends + at END_ELEM. The result are written to MBCSET and SBCSET. + RANGE_ALLOC is the allocated size of mbcset->range_starts, and + mbcset->range_ends, is a pointer argument sinse we may + update it. */ + +static reg_errcode_t +internal_function +# ifdef RE_ENABLE_I18N +build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc, + bracket_elem_t *start_elem, bracket_elem_t *end_elem) +# else /* not RE_ENABLE_I18N */ +build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem, + bracket_elem_t *end_elem) +# endif /* not RE_ENABLE_I18N */ +{ + unsigned int start_ch, end_ch; + /* Equivalence Classes and Character Classes can't be a range start/end. */ + if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS + || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, + 0)) + return REG_ERANGE; + + /* We can handle no multi character collating elements without libc + support. */ + if (BE ((start_elem->type == COLL_SYM + && strlen ((char *) start_elem->opr.name) > 1) + || (end_elem->type == COLL_SYM + && strlen ((char *) end_elem->opr.name) > 1), 0)) + return REG_ECOLLATE; + +# ifdef RE_ENABLE_I18N + { + wchar_t wc; + wint_t start_wc; + wint_t end_wc; + wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; + + start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch + : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] + : 0)); + end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch + : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] + : 0)); +#ifdef GAWK + /* + * Fedora Core 2, maybe others, have broken `btowc' that returns -1 + * for any value > 127. Sigh. Note that `start_ch' and `end_ch' are + * unsigned, so we don't have sign extension problems. + */ + start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM) + ? start_ch : start_elem->opr.wch); + end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) + ? end_ch : end_elem->opr.wch); +#else + start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM) + ? __btowc (start_ch) : start_elem->opr.wch); + end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM) + ? __btowc (end_ch) : end_elem->opr.wch); +#endif + if (start_wc == WEOF || end_wc == WEOF) + return REG_ECOLLATE; + cmp_buf[0] = start_wc; + cmp_buf[4] = end_wc; + if (wcscoll (cmp_buf, cmp_buf + 4) > 0) + return REG_ERANGE; + + /* Got valid collation sequence values, add them as a new entry. + However, for !_LIBC we have no collation elements: if the + character set is single byte, the single byte character set + that we build below suffices. parse_bracket_exp passes + no MBCSET if dfa->mb_cur_max == 1. */ + if (mbcset) + { + /* Check the space of the arrays. */ + if (BE (*range_alloc == mbcset->nranges, 0)) + { + /* There is not enough space, need realloc. */ + wchar_t *new_array_start, *new_array_end; + int new_nranges; + + /* +1 in case of mbcset->nranges is 0. */ + new_nranges = 2 * mbcset->nranges + 1; + /* Use realloc since mbcset->range_starts and mbcset->range_ends + are NULL if *range_alloc == 0. */ + new_array_start = re_realloc (mbcset->range_starts, wchar_t, + new_nranges); + new_array_end = re_realloc (mbcset->range_ends, wchar_t, + new_nranges); + + if (BE (new_array_start == NULL || new_array_end == NULL, 0)) + return REG_ESPACE; + + mbcset->range_starts = new_array_start; + mbcset->range_ends = new_array_end; + *range_alloc = new_nranges; + } + + mbcset->range_starts[mbcset->nranges] = start_wc; + mbcset->range_ends[mbcset->nranges++] = end_wc; + } + + /* Build the table for single byte characters. */ + for (wc = 0; wc < SBC_MAX; ++wc) + { + cmp_buf[2] = wc; + if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 + && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) + bitset_set (sbcset, wc); + } + } +# else /* not RE_ENABLE_I18N */ + { + unsigned int ch; + start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch + : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0] + : 0)); + end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch + : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0] + : 0)); + if (start_ch > end_ch) + return REG_ERANGE; + /* Build the table for single byte characters. */ + for (ch = 0; ch < SBC_MAX; ++ch) + if (start_ch <= ch && ch <= end_ch) + bitset_set (sbcset, ch); + } +# endif /* not RE_ENABLE_I18N */ + return REG_NOERROR; +} +#endif /* not _LIBC */ + +#ifndef _LIBC +/* Helper function for parse_bracket_exp only used in case of NOT _LIBC.. + Build the collating element which is represented by NAME. + The result are written to MBCSET and SBCSET. + COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a + pointer argument since we may update it. */ + +static reg_errcode_t +internal_function +# ifdef RE_ENABLE_I18N +build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset, + int *coll_sym_alloc, const unsigned char *name) +# else /* not RE_ENABLE_I18N */ +build_collating_symbol (bitset_t sbcset, const unsigned char *name) +# endif /* not RE_ENABLE_I18N */ +{ + size_t name_len = strlen ((const char *) name); + if (BE (name_len != 1, 0)) + return REG_ECOLLATE; + else + { + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } +} +#endif /* not _LIBC */ + +/* This function parse bracket expression like "[abc]", "[a-c]", + "[[.a-a.]]" etc. */ + +static bin_tree_t * +parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, + reg_syntax_t syntax, reg_errcode_t *err) +{ +#ifdef _LIBC + const unsigned char *collseqmb; + const char *collseqwc; + uint32_t nrules; + int32_t table_size; + const int32_t *symb_table; + const unsigned char *extra; + + /* Local function for parse_bracket_exp used in _LIBC environement. + Seek the collating symbol entry correspondings to NAME. + Return the index of the symbol in the SYMB_TABLE. */ + + auto inline int32_t + __attribute ((always_inline)) + seek_collating_symbol_entry (name, name_len) + const unsigned char *name; + size_t name_len; + { + int32_t hash = elem_hash ((const char *) name, name_len); + int32_t elem = hash % table_size; + if (symb_table[2 * elem] != 0) + { + int32_t second = hash % (table_size - 2) + 1; + + do + { + /* First compare the hashing value. */ + if (symb_table[2 * elem] == hash + /* Compare the length of the name. */ + && name_len == extra[symb_table[2 * elem + 1]] + /* Compare the name. */ + && memcmp (name, &extra[symb_table[2 * elem + 1] + 1], + name_len) == 0) + { + /* Yep, this is the entry. */ + break; + } + + /* Next entry. */ + elem += second; + } + while (symb_table[2 * elem] != 0); + } + return elem; + } + + /* Local function for parse_bracket_exp used in _LIBC environment. + Look up the collation sequence value of BR_ELEM. + Return the value if succeeded, UINT_MAX otherwise. */ + + auto inline unsigned int + __attribute ((always_inline)) + lookup_collation_sequence_value (br_elem) + bracket_elem_t *br_elem; + { + if (br_elem->type == SB_CHAR) + { + /* + if (MB_CUR_MAX == 1) + */ + if (nrules == 0) + return collseqmb[br_elem->opr.ch]; + else + { + wint_t wc = __btowc (br_elem->opr.ch); + return __collseq_table_lookup (collseqwc, wc); + } + } + else if (br_elem->type == MB_CHAR) + { + if (nrules != 0) + return __collseq_table_lookup (collseqwc, br_elem->opr.wch); + } + else if (br_elem->type == COLL_SYM) + { + size_t sym_name_len = strlen ((char *) br_elem->opr.name); + if (nrules != 0) + { + int32_t elem, idx; + elem = seek_collating_symbol_entry (br_elem->opr.name, + sym_name_len); + if (symb_table[2 * elem] != 0) + { + /* We found the entry. */ + idx = symb_table[2 * elem + 1]; + /* Skip the name of collating element name. */ + idx += 1 + extra[idx]; + /* Skip the byte sequence of the collating element. */ + idx += 1 + extra[idx]; + /* Adjust for the alignment. */ + idx = (idx + 3) & ~3; + /* Skip the multibyte collation sequence value. */ + idx += sizeof (unsigned int); + /* Skip the wide char sequence of the collating element. */ + idx += sizeof (unsigned int) * + (1 + *(unsigned int *) (extra + idx)); + /* Return the collation sequence value. */ + return *(unsigned int *) (extra + idx); + } + else if (symb_table[2 * elem] == 0 && sym_name_len == 1) + { + /* No valid character. Match it as a single byte + character. */ + return collseqmb[br_elem->opr.name[0]]; + } + } + else if (sym_name_len == 1) + return collseqmb[br_elem->opr.name[0]]; + } + return UINT_MAX; + } + + /* Local function for parse_bracket_exp used in _LIBC environement. + Build the range expression which starts from START_ELEM, and ends + at END_ELEM. The result are written to MBCSET and SBCSET. + RANGE_ALLOC is the allocated size of mbcset->range_starts, and + mbcset->range_ends, is a pointer argument sinse we may + update it. */ + + auto inline reg_errcode_t + __attribute ((always_inline)) + build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem) + re_charset_t *mbcset; + int *range_alloc; + bitset_t sbcset; + bracket_elem_t *start_elem, *end_elem; + { + unsigned int ch; + uint32_t start_collseq; + uint32_t end_collseq; + + /* Equivalence Classes and Character Classes can't be a range + start/end. */ + if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS + || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS, + 0)) + return REG_ERANGE; + + start_collseq = lookup_collation_sequence_value (start_elem); + end_collseq = lookup_collation_sequence_value (end_elem); + /* Check start/end collation sequence values. */ + if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0)) + return REG_ECOLLATE; + if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0)) + return REG_ERANGE; + + /* Got valid collation sequence values, add them as a new entry. + However, if we have no collation elements, and the character set + is single byte, the single byte character set that we + build below suffices. */ + if (nrules > 0 || dfa->mb_cur_max > 1) + { + /* Check the space of the arrays. */ + if (BE (*range_alloc == mbcset->nranges, 0)) + { + /* There is not enough space, need realloc. */ + uint32_t *new_array_start; + uint32_t *new_array_end; + int new_nranges; + + /* +1 in case of mbcset->nranges is 0. */ + new_nranges = 2 * mbcset->nranges + 1; + new_array_start = re_realloc (mbcset->range_starts, uint32_t, + new_nranges); + new_array_end = re_realloc (mbcset->range_ends, uint32_t, + new_nranges); + + if (BE (new_array_start == NULL || new_array_end == NULL, 0)) + return REG_ESPACE; + + mbcset->range_starts = new_array_start; + mbcset->range_ends = new_array_end; + *range_alloc = new_nranges; + } + + mbcset->range_starts[mbcset->nranges] = start_collseq; + mbcset->range_ends[mbcset->nranges++] = end_collseq; + } + + /* Build the table for single byte characters. */ + for (ch = 0; ch < SBC_MAX; ch++) + { + uint32_t ch_collseq; + /* + if (MB_CUR_MAX == 1) + */ + if (nrules == 0) + ch_collseq = collseqmb[ch]; + else + ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch)); + if (start_collseq <= ch_collseq && ch_collseq <= end_collseq) + bitset_set (sbcset, ch); + } + return REG_NOERROR; + } + + /* Local function for parse_bracket_exp used in _LIBC environement. + Build the collating element which is represented by NAME. + The result are written to MBCSET and SBCSET. + COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a + pointer argument sinse we may update it. */ + + auto inline reg_errcode_t + __attribute ((always_inline)) + build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name) + re_charset_t *mbcset; + int *coll_sym_alloc; + bitset_t sbcset; + const unsigned char *name; + { + int32_t elem, idx; + size_t name_len = strlen ((const char *) name); + if (nrules != 0) + { + elem = seek_collating_symbol_entry (name, name_len); + if (symb_table[2 * elem] != 0) + { + /* We found the entry. */ + idx = symb_table[2 * elem + 1]; + /* Skip the name of collating element name. */ + idx += 1 + extra[idx]; + } + else if (symb_table[2 * elem] == 0 && name_len == 1) + { + /* No valid character, treat it as a normal + character. */ + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } + else + return REG_ECOLLATE; + + /* Got valid collation sequence, add it as a new entry. */ + /* Check the space of the arrays. */ + if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->ncoll_syms is 0. */ + int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1; + /* Use realloc since mbcset->coll_syms is NULL + if *alloc == 0. */ + int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t, + new_coll_sym_alloc); + if (BE (new_coll_syms == NULL, 0)) + return REG_ESPACE; + mbcset->coll_syms = new_coll_syms; + *coll_sym_alloc = new_coll_sym_alloc; + } + mbcset->coll_syms[mbcset->ncoll_syms++] = idx; + return REG_NOERROR; + } + else + { + if (BE (name_len != 1, 0)) + return REG_ECOLLATE; + else + { + bitset_set (sbcset, name[0]); + return REG_NOERROR; + } + } + } +#endif + + re_token_t br_token; + re_bitset_ptr_t sbcset; +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; + int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0; + int equiv_class_alloc = 0, char_class_alloc = 0; +#endif /* not RE_ENABLE_I18N */ + int non_match = 0; + bin_tree_t *work_tree; + int token_len; + int first_round = 1; +#ifdef _LIBC + collseqmb = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); + nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules) + { + /* + if (MB_CUR_MAX > 1) + */ + collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); + table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); + symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_SYMB_TABLEMB); + extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_SYMB_EXTRAMB); + } +#endif + sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); +#ifdef RE_ENABLE_I18N + mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); +#endif /* RE_ENABLE_I18N */ +#ifdef RE_ENABLE_I18N + if (BE (sbcset == NULL || mbcset == NULL, 0)) +#else + if (BE (sbcset == NULL, 0)) +#endif /* RE_ENABLE_I18N */ + { + *err = REG_ESPACE; + return NULL; + } + + token_len = peek_token_bracket (token, regexp, syntax); + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_BADPAT; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_NON_MATCH_LIST) + { +#ifdef RE_ENABLE_I18N + mbcset->non_match = 1; +#endif /* not RE_ENABLE_I18N */ + non_match = 1; + if (syntax & RE_HAT_LISTS_NOT_NEWLINE) + bitset_set (sbcset, '\n'); + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + token_len = peek_token_bracket (token, regexp, syntax); + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_BADPAT; + goto parse_bracket_exp_free_return; + } + } + + /* We treat the first ']' as a normal character. */ + if (token->type == OP_CLOSE_BRACKET) + token->type = CHARACTER; + + while (1) + { + bracket_elem_t start_elem, end_elem; + unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE]; + unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE]; + reg_errcode_t ret; + int token_len2 = 0, is_range_exp = 0; + re_token_t token2; + + start_elem.opr.name = start_name_buf; + ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa, + syntax, first_round); + if (BE (ret != REG_NOERROR, 0)) + { + *err = ret; + goto parse_bracket_exp_free_return; + } + first_round = 0; + + /* Get information about the next token. We need it in any case. */ + token_len = peek_token_bracket (token, regexp, syntax); + + /* Do not check for ranges if we know they are not allowed. */ + if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS) + { + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_CHARSET_RANGE) + { + re_string_skip_bytes (regexp, token_len); /* Skip '-'. */ + token_len2 = peek_token_bracket (&token2, regexp, syntax); + if (BE (token2.type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token2.type == OP_CLOSE_BRACKET) + { + /* We treat the last '-' as a normal character. */ + re_string_skip_bytes (regexp, -token_len); + token->type = CHARACTER; + } + else + is_range_exp = 1; + } + } + + if (is_range_exp == 1) + { + end_elem.opr.name = end_name_buf; + ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2, + dfa, syntax, 1); + if (BE (ret != REG_NOERROR, 0)) + { + *err = ret; + goto parse_bracket_exp_free_return; + } + + token_len = peek_token_bracket (token, regexp, syntax); + +#ifdef _LIBC + *err = build_range_exp (sbcset, mbcset, &range_alloc, + &start_elem, &end_elem); +#else +# ifdef RE_ENABLE_I18N + *err = build_range_exp (sbcset, + dfa->mb_cur_max > 1 ? mbcset : NULL, + &range_alloc, &start_elem, &end_elem); +# else + *err = build_range_exp (sbcset, &start_elem, &end_elem); +# endif +#endif /* RE_ENABLE_I18N */ + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + } + else + { + switch (start_elem.type) + { + case SB_CHAR: + bitset_set (sbcset, start_elem.opr.ch); + break; +#ifdef RE_ENABLE_I18N + case MB_CHAR: + /* Check whether the array has enough space. */ + if (BE (mbchar_alloc == mbcset->nmbchars, 0)) + { + wchar_t *new_mbchars; + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nmbchars is 0. */ + mbchar_alloc = 2 * mbcset->nmbchars + 1; + /* Use realloc since array is NULL if *alloc == 0. */ + new_mbchars = re_realloc (mbcset->mbchars, wchar_t, + mbchar_alloc); + if (BE (new_mbchars == NULL, 0)) + goto parse_bracket_exp_espace; + mbcset->mbchars = new_mbchars; + } + mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch; + break; +#endif /* RE_ENABLE_I18N */ + case EQUIV_CLASS: + *err = build_equiv_class (sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &equiv_class_alloc, +#endif /* RE_ENABLE_I18N */ + start_elem.opr.name); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + case COLL_SYM: + *err = build_collating_symbol (sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &coll_sym_alloc, +#endif /* RE_ENABLE_I18N */ + start_elem.opr.name); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + case CHAR_CLASS: + *err = build_charclass (regexp->trans, sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &char_class_alloc, +#endif /* RE_ENABLE_I18N */ + (const char *) start_elem.opr.name, syntax); + if (BE (*err != REG_NOERROR, 0)) + goto parse_bracket_exp_free_return; + break; + default: + assert (0); + break; + } + } + if (BE (token->type == END_OF_RE, 0)) + { + *err = REG_EBRACK; + goto parse_bracket_exp_free_return; + } + if (token->type == OP_CLOSE_BRACKET) + break; + } + + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + + /* If it is non-matching list. */ + if (non_match) + bitset_not (sbcset); + +#ifdef RE_ENABLE_I18N + /* Ensure only single byte characters are set. */ + if (dfa->mb_cur_max > 1) + bitset_mask (sbcset, dfa->sb_char); + + if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes + || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes + || mbcset->non_match))) + { + bin_tree_t *mbc_tree; + int sbc_idx; + /* Build a tree for complex bracket. */ + dfa->has_mb_node = 1; + br_token.type = COMPLEX_BRACKET; + br_token.opr.mbcset = mbcset; + mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); + if (BE (mbc_tree == NULL, 0)) + goto parse_bracket_exp_espace; + for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx) + if (sbcset[sbc_idx]) + break; + /* If there are no bits set in sbcset, there is no point + of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */ + if (sbc_idx < BITSET_WORDS) + { + /* Build a tree for simple bracket. */ + br_token.type = SIMPLE_BRACKET; + br_token.opr.sbcset = sbcset; + work_tree = create_token_tree (dfa, NULL, NULL, &br_token); + if (BE (work_tree == NULL, 0)) + goto parse_bracket_exp_espace; + + /* Then join them by ALT node. */ + work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT); + if (BE (work_tree == NULL, 0)) + goto parse_bracket_exp_espace; + } + else + { + re_free (sbcset); + work_tree = mbc_tree; + } + } + else +#endif /* not RE_ENABLE_I18N */ + { +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif + /* Build a tree for simple bracket. */ + br_token.type = SIMPLE_BRACKET; + br_token.opr.sbcset = sbcset; + work_tree = create_token_tree (dfa, NULL, NULL, &br_token); + if (BE (work_tree == NULL, 0)) + goto parse_bracket_exp_espace; + } + return work_tree; + + parse_bracket_exp_espace: + *err = REG_ESPACE; + parse_bracket_exp_free_return: + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + return NULL; +} + +/* Parse an element in the bracket expression. */ + +static reg_errcode_t +parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp, + re_token_t *token, int token_len, re_dfa_t *dfa, + reg_syntax_t syntax, int accept_hyphen) +{ +#ifdef RE_ENABLE_I18N + int cur_char_size; + cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp)); + if (cur_char_size > 1) + { + elem->type = MB_CHAR; + elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp)); + re_string_skip_bytes (regexp, cur_char_size); + return REG_NOERROR; + } +#endif /* RE_ENABLE_I18N */ + re_string_skip_bytes (regexp, token_len); /* Skip a token. */ + if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS + || token->type == OP_OPEN_EQUIV_CLASS) + return parse_bracket_symbol (elem, regexp, token); + if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen) + { + /* A '-' must only appear as anything but a range indicator before + the closing bracket. Everything else is an error. */ + re_token_t token2; + (void) peek_token_bracket (&token2, regexp, syntax); + if (token2.type != OP_CLOSE_BRACKET) + /* The actual error value is not standardized since this whole + case is undefined. But ERANGE makes good sense. */ + return REG_ERANGE; + } + elem->type = SB_CHAR; + elem->opr.ch = token->opr.c; + return REG_NOERROR; +} + +/* Parse a bracket symbol in the bracket expression. Bracket symbols are + such as [::], [..], and + [==]. */ + +static reg_errcode_t +parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp, + re_token_t *token) +{ + unsigned char ch, delim = token->opr.c; + int i = 0; + if (re_string_eoi(regexp)) + return REG_EBRACK; + for (;; ++i) + { + if (i >= BRACKET_NAME_BUF_SIZE) + return REG_EBRACK; + if (token->type == OP_OPEN_CHAR_CLASS) + ch = re_string_fetch_byte_case (regexp); + else + ch = re_string_fetch_byte (regexp); + if (re_string_eoi(regexp)) + return REG_EBRACK; + if (ch == delim && re_string_peek_byte (regexp, 0) == ']') + break; + elem->opr.name[i] = ch; + } + re_string_skip_bytes (regexp, 1); + elem->opr.name[i] = '\0'; + switch (token->type) + { + case OP_OPEN_COLL_ELEM: + elem->type = COLL_SYM; + break; + case OP_OPEN_EQUIV_CLASS: + elem->type = EQUIV_CLASS; + break; + case OP_OPEN_CHAR_CLASS: + elem->type = CHAR_CLASS; + break; + default: + break; + } + return REG_NOERROR; +} + + /* Helper function for parse_bracket_exp. + Build the equivalence class which is represented by NAME. + The result are written to MBCSET and SBCSET. + EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes, + is a pointer argument sinse we may update it. */ + +static reg_errcode_t +#ifdef RE_ENABLE_I18N +build_equiv_class (bitset_t sbcset, re_charset_t *mbcset, + int *equiv_class_alloc, const unsigned char *name) +#else /* not RE_ENABLE_I18N */ +build_equiv_class (bitset_t sbcset, const unsigned char *name) +#endif /* not RE_ENABLE_I18N */ +{ +#ifdef _LIBC + uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules != 0) + { + const int32_t *table, *indirect; + const unsigned char *weights, *extra, *cp; + unsigned char char_buf[2]; + int32_t idx1, idx2; + unsigned int ch; + size_t len; + /* This #include defines a local function! */ +# include + /* Calculate the index for equivalence class. */ + cp = name; + table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_WEIGHTMB); + extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_INDIRECTMB); + idx1 = findidx (&cp); + if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0)) + /* This isn't a valid character. */ + return REG_ECOLLATE; + + /* Build single byte matcing table for this equivalence class. */ + char_buf[1] = (unsigned char) '\0'; + len = weights[idx1 & 0xffffff]; + for (ch = 0; ch < SBC_MAX; ++ch) + { + char_buf[0] = ch; + cp = char_buf; + idx2 = findidx (&cp); +/* + idx2 = table[ch]; +*/ + if (idx2 == 0) + /* This isn't a valid character. */ + continue; + /* Compare only if the length matches and the collation rule + index is the same. */ + if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24)) + { + int cnt = 0; + + while (cnt <= len && + weights[(idx1 & 0xffffff) + 1 + cnt] + == weights[(idx2 & 0xffffff) + 1 + cnt]) + ++cnt; + + if (cnt > len) + bitset_set (sbcset, ch); + } + } + /* Check whether the array has enough space. */ + if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nequiv_classes is 0. */ + int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1; + /* Use realloc since the array is NULL if *alloc == 0. */ + int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes, + int32_t, + new_equiv_class_alloc); + if (BE (new_equiv_classes == NULL, 0)) + return REG_ESPACE; + mbcset->equiv_classes = new_equiv_classes; + *equiv_class_alloc = new_equiv_class_alloc; + } + mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1; + } + else +#endif /* _LIBC */ + { + if (BE (strlen ((const char *) name) != 1, 0)) + return REG_ECOLLATE; + bitset_set (sbcset, *name); + } + return REG_NOERROR; +} + + /* Helper function for parse_bracket_exp. + Build the character class which is represented by NAME. + The result are written to MBCSET and SBCSET. + CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes, + is a pointer argument sinse we may update it. */ + +static reg_errcode_t +#ifdef RE_ENABLE_I18N +build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, + re_charset_t *mbcset, int *char_class_alloc, + const char *class_name, reg_syntax_t syntax) +#else /* not RE_ENABLE_I18N */ +build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset, + const char *class_name, reg_syntax_t syntax) +#endif /* not RE_ENABLE_I18N */ +{ + int i; + + /* In case of REG_ICASE "upper" and "lower" match the both of + upper and lower cases. */ + if ((syntax & RE_ICASE) + && (strcmp (class_name, "upper") == 0 || strcmp (class_name, "lower") == 0)) + class_name = "alpha"; + +#ifdef RE_ENABLE_I18N + /* Check the space of the arrays. */ + if (BE (*char_class_alloc == mbcset->nchar_classes, 0)) + { + /* Not enough, realloc it. */ + /* +1 in case of mbcset->nchar_classes is 0. */ + int new_char_class_alloc = 2 * mbcset->nchar_classes + 1; + /* Use realloc since array is NULL if *alloc == 0. */ + wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t, + new_char_class_alloc); + if (BE (new_char_classes == NULL, 0)) + return REG_ESPACE; + mbcset->char_classes = new_char_classes; + *char_class_alloc = new_char_class_alloc; + } + mbcset->char_classes[mbcset->nchar_classes++] = __wctype (class_name); +#endif /* RE_ENABLE_I18N */ + +#define BUILD_CHARCLASS_LOOP(ctype_func) \ + do { \ + if (BE (trans != NULL, 0)) \ + { \ + for (i = 0; i < SBC_MAX; ++i) \ + if (ctype_func (i)) \ + bitset_set (sbcset, trans[i]); \ + } \ + else \ + { \ + for (i = 0; i < SBC_MAX; ++i) \ + if (ctype_func (i)) \ + bitset_set (sbcset, i); \ + } \ + } while (0) + + if (strcmp (class_name, "alnum") == 0) + BUILD_CHARCLASS_LOOP (isalnum); + else if (strcmp (class_name, "cntrl") == 0) + BUILD_CHARCLASS_LOOP (iscntrl); + else if (strcmp (class_name, "lower") == 0) + BUILD_CHARCLASS_LOOP (islower); + else if (strcmp (class_name, "space") == 0) + BUILD_CHARCLASS_LOOP (isspace); + else if (strcmp (class_name, "alpha") == 0) + BUILD_CHARCLASS_LOOP (isalpha); + else if (strcmp (class_name, "digit") == 0) + BUILD_CHARCLASS_LOOP (isdigit); + else if (strcmp (class_name, "print") == 0) + BUILD_CHARCLASS_LOOP (isprint); + else if (strcmp (class_name, "upper") == 0) + BUILD_CHARCLASS_LOOP (isupper); + else if (strcmp (class_name, "blank") == 0) +#ifndef GAWK + BUILD_CHARCLASS_LOOP (isblank); +#else + /* see comments above */ + BUILD_CHARCLASS_LOOP (is_blank); +#endif + else if (strcmp (class_name, "graph") == 0) + BUILD_CHARCLASS_LOOP (isgraph); + else if (strcmp (class_name, "punct") == 0) + BUILD_CHARCLASS_LOOP (ispunct); + else if (strcmp (class_name, "xdigit") == 0) + BUILD_CHARCLASS_LOOP (isxdigit); + else + return REG_ECTYPE; + + return REG_NOERROR; +} + +static bin_tree_t * +build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, + const char *class_name, + const char *extra, int non_match, + reg_errcode_t *err) +{ + re_bitset_ptr_t sbcset; +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; + int alloc = 0; +#endif /* not RE_ENABLE_I18N */ + reg_errcode_t ret; + re_token_t br_token; + bin_tree_t *tree; + + sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); +#ifdef RE_ENABLE_I18N + mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); +#endif /* RE_ENABLE_I18N */ + +#ifdef RE_ENABLE_I18N + if (BE (sbcset == NULL || mbcset == NULL, 0)) +#else /* not RE_ENABLE_I18N */ + if (BE (sbcset == NULL, 0)) +#endif /* not RE_ENABLE_I18N */ + { + *err = REG_ESPACE; + return NULL; + } + + if (non_match) + { +#ifdef RE_ENABLE_I18N + mbcset->non_match = 1; +#endif /* not RE_ENABLE_I18N */ + } + + /* We don't care the syntax in this case. */ + ret = build_charclass (trans, sbcset, +#ifdef RE_ENABLE_I18N + mbcset, &alloc, +#endif /* RE_ENABLE_I18N */ + class_name, 0); + + if (BE (ret != REG_NOERROR, 0)) + { + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + *err = ret; + return NULL; + } + /* \w match '_' also. */ + for (; *extra; extra++) + bitset_set (sbcset, *extra); + + /* If it is non-matching list. */ + if (non_match) + bitset_not (sbcset); + +#ifdef RE_ENABLE_I18N + /* Ensure only single byte characters are set. */ + if (dfa->mb_cur_max > 1) + bitset_mask (sbcset, dfa->sb_char); +#endif + + /* Build a tree for simple bracket. */ + br_token.type = SIMPLE_BRACKET; + br_token.opr.sbcset = sbcset; + tree = create_token_tree (dfa, NULL, NULL, &br_token); + if (BE (tree == NULL, 0)) + goto build_word_op_espace; + +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + bin_tree_t *mbc_tree; + /* Build a tree for complex bracket. */ + br_token.type = COMPLEX_BRACKET; + br_token.opr.mbcset = mbcset; + dfa->has_mb_node = 1; + mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token); + if (BE (mbc_tree == NULL, 0)) + goto build_word_op_espace; + /* Then join them by ALT node. */ + tree = create_tree (dfa, tree, mbc_tree, OP_ALT); + if (BE (mbc_tree != NULL, 1)) + return tree; + } + else + { + free_charset (mbcset); + return tree; + } +#else /* not RE_ENABLE_I18N */ + return tree; +#endif /* not RE_ENABLE_I18N */ + + build_word_op_espace: + re_free (sbcset); +#ifdef RE_ENABLE_I18N + free_charset (mbcset); +#endif /* RE_ENABLE_I18N */ + *err = REG_ESPACE; + return NULL; +} + +/* This is intended for the expressions like "a{1,3}". + Fetch a number from `input', and return the number. + Return -1, if the number field is empty like "{,1}". + Return -2, If an error is occured. */ + +static int +fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) +{ + int num = -1; + unsigned char c; + while (1) + { + fetch_token (token, input, syntax); + c = token->opr.c; + if (BE (token->type == END_OF_RE, 0)) + return -2; + if (token->type == OP_CLOSE_DUP_NUM || c == ',') + break; + num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2) + ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0')); + num = (num > RE_DUP_MAX) ? -2 : num; + } + return num; +} + +#ifdef RE_ENABLE_I18N +static void +free_charset (re_charset_t *cset) +{ + re_free (cset->mbchars); +# ifdef _LIBC + re_free (cset->coll_syms); + re_free (cset->equiv_classes); + re_free (cset->range_starts); + re_free (cset->range_ends); +# endif + re_free (cset->char_classes); + re_free (cset); +} +#endif /* RE_ENABLE_I18N */ + +/* Functions for binary tree operation. */ + +/* Create a tree node. */ + +static bin_tree_t * +create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, + re_token_type_t type) +{ + re_token_t t; + t.type = type; + return create_token_tree (dfa, left, right, &t); +} + +static bin_tree_t * +create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, + const re_token_t *token) +{ + bin_tree_t *tree; + if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0)) + { + bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1); + + if (storage == NULL) + return NULL; + storage->next = dfa->str_tree_storage; + dfa->str_tree_storage = storage; + dfa->str_tree_storage_idx = 0; + } + tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++]; + + tree->parent = NULL; + tree->left = left; + tree->right = right; + tree->token = *token; + tree->token.duplicated = 0; + tree->token.opt_subexp = 0; + tree->first = NULL; + tree->next = NULL; + tree->node_idx = -1; + + if (left != NULL) + left->parent = tree; + if (right != NULL) + right->parent = tree; + return tree; +} + +/* Mark the tree SRC as an optional subexpression. + To be called from preorder or postorder. */ + +static reg_errcode_t +mark_opt_subexp (void *extra, bin_tree_t *node) +{ + int idx = (int) (long) extra; + if (node->token.type == SUBEXP && node->token.opr.idx == idx) + node->token.opt_subexp = 1; + + return REG_NOERROR; +} + +/* Free the allocated memory inside NODE. */ + +static void +free_token (re_token_t *node) +{ +#ifdef RE_ENABLE_I18N + if (node->type == COMPLEX_BRACKET && node->duplicated == 0) + free_charset (node->opr.mbcset); + else +#endif /* RE_ENABLE_I18N */ + if (node->type == SIMPLE_BRACKET && node->duplicated == 0) + re_free (node->opr.sbcset); +} + +/* Worker function for tree walking. Free the allocated memory inside NODE + and its children. */ + +static reg_errcode_t +free_tree (void *extra, bin_tree_t *node) +{ + free_token (&node->token); + return REG_NOERROR; +} + + +/* Duplicate the node SRC, and return new node. This is a preorder + visit similar to the one implemented by the generic visitor, but + we need more infrastructure to maintain two parallel trees --- so, + it's easier to duplicate. */ + +static bin_tree_t * +duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa) +{ + const bin_tree_t *node; + bin_tree_t *dup_root; + bin_tree_t **p_new = &dup_root, *dup_node = root->parent; + + for (node = root; ; ) + { + /* Create a new tree and link it back to the current parent. */ + *p_new = create_token_tree (dfa, NULL, NULL, &node->token); + if (*p_new == NULL) + return NULL; + (*p_new)->parent = dup_node; + (*p_new)->token.duplicated = 1; + dup_node = *p_new; + + /* Go to the left node, or up and to the right. */ + if (node->left) + { + node = node->left; + p_new = &dup_node->left; + } + else + { + const bin_tree_t *prev = NULL; + while (node->right == prev || node->right == NULL) + { + prev = node; + node = node->parent; + dup_node = dup_node->parent; + if (!node) + return dup_root; + } + node = node->right; + p_new = &dup_node->right; + } + } +} diff --git a/deps/regex/regex.c b/deps/regex/regex.c new file mode 100644 index 000000000..f9a8c9bf1 --- /dev/null +++ b/deps/regex/regex.c @@ -0,0 +1,85 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +#include "config.h" + +/* Make sure noone compiles this code with a C++ compiler. */ +#ifdef __cplusplus +# error "This is C code, use a C compiler" +#endif + +#ifdef _LIBC +/* We have to keep the namespace clean. */ +# define regfree(preg) __regfree (preg) +# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) +# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) +# define regerror(errcode, preg, errbuf, errbuf_size) \ + __regerror(errcode, preg, errbuf, errbuf_size) +# define re_set_registers(bu, re, nu, st, en) \ + __re_set_registers (bu, re, nu, st, en) +# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ + __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) +# define re_match(bufp, string, size, pos, regs) \ + __re_match (bufp, string, size, pos, regs) +# define re_search(bufp, string, size, startpos, range, regs) \ + __re_search (bufp, string, size, startpos, range, regs) +# define re_compile_pattern(pattern, length, bufp) \ + __re_compile_pattern (pattern, length, bufp) +# define re_set_syntax(syntax) __re_set_syntax (syntax) +# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ + __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) +# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) + +# include "../locale/localeinfo.h" +#endif + +#if defined (_MSC_VER) +#include /* for size_t */ +#endif + +/* On some systems, limits.h sets RE_DUP_MAX to a lower value than + GNU regex allows. Include it before , which correctly + #undefs RE_DUP_MAX and sets it to the right value. */ +#include + +#ifdef GAWK +#undef alloca +#define alloca alloca_is_bad_you_should_never_use_it +#endif +#include +#include "regex_internal.h" + +#include "regex_internal.c" +#ifdef GAWK +#define bool int +#define true (1) +#define false (0) +#endif +#include "regcomp.c" +#include "regexec.c" + +/* Binary backward compatibility. */ +#if _LIBC +# include +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3) +link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.") +int re_max_failures = 2000; +# endif +#endif diff --git a/deps/regex/regex.h b/deps/regex/regex.h new file mode 100644 index 000000000..61c968387 --- /dev/null +++ b/deps/regex/regex.h @@ -0,0 +1,582 @@ +#include +#include + +/* Definitions for data structures and routines for the regular + expression library. + Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +#ifndef _REGEX_H +#define _REGEX_H 1 + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifndef _LIBC +#define __USE_GNU 1 +#endif + +/* Allow the use in C++ code. */ +#ifdef __cplusplus +extern "C" { +#endif + +/* The following two types have to be signed and unsigned integer type + wide enough to hold a value of a pointer. For most ANSI compilers + ptrdiff_t and size_t should be likely OK. Still size of these two + types is 2 for Microsoft C. Ugh... */ +typedef long int s_reg_t; +typedef unsigned long int active_reg_t; + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned long int reg_syntax_t; + +#ifdef __USE_GNU +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +# define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +# define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ +# define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +# define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ +# define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ +# define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ +# define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +# define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +# define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +# define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +# define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, then `{...}' defines an interval, and \{ and \} + are literals. + If not set, then `\{...\}' defines an interval. */ +# define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +# define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then \ matches . + If not set, then \ is a back-reference. */ +# define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +# define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ +# define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ +# define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) + +/* If this bit is set, succeed as soon as we match the whole pattern, + without further backtracking. */ +# define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) + +/* If this bit is set, do not process the GNU regex operators. + If not set, then the GNU regex operators are recognized. */ +# define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) + +/* If this bit is set, a syntactically invalid interval is treated as + a string of ordinary characters. For example, the ERE 'a{1' is + treated as 'a\{1'. */ +# define RE_INVALID_INTERVAL_ORD (RE_NO_GNU_OPS << 1) + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +# define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) + +/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only + for ^, because it is difficult to scan the regex backwards to find + whether ^ should be special. */ +# define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) + +/* If this bit is set, then \{ cannot be first in an bre or + immediately after an alternation or begin-group operator. */ +# define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) + +/* If this bit is set, then no_sub will be set to 1 during + re_compile_pattern. */ +#define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1) +#endif + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t re_syntax_options; + +#ifdef __USE_GNU +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file, so + don't delete them!) */ +/* [[[begin syntaxes]]] */ +#define RE_SYNTAX_EMACS 0 + +#define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ + | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ + | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) + +#define RE_SYNTAX_GNU_AWK \ + ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INVALID_INTERVAL_ORD) \ + & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS \ + | RE_CONTEXT_INVALID_OPS )) + +#define RE_SYNTAX_POSIX_AWK \ + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INTERVALS | RE_NO_GNU_OPS \ + | RE_INVALID_INTERVAL_ORD) + +#define RE_SYNTAX_GREP \ + (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ + | RE_NEWLINE_ALT) + +#define RE_SYNTAX_EGREP \ + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ + | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ + | RE_NO_BK_VBAR) + +#define RE_SYNTAX_POSIX_EGREP \ + (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ + | RE_INVALID_INTERVAL_ORD) + +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ +#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC + +#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC + +/* Syntax bits common to both basic and extended POSIX regex syntax. */ +#define _RE_SYNTAX_POSIX_COMMON \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NO_EMPTY_RANGES) + +#define RE_SYNTAX_POSIX_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) + +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this + isn't minimal, since other operators, such as \`, aren't disabled. */ +#define RE_SYNTAX_POSIX_MINIMAL_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) + +#define RE_SYNTAX_POSIX_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ + | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) + +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is + removed and RE_NO_BK_REFS is added. */ +#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + +/* Maximum number of duplicates an interval can allow. Some systems + (erroneously) define this in other header files, but we want our + value, so remove any previous define. */ +# ifdef RE_DUP_MAX +# undef RE_DUP_MAX +# endif +/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ +# define RE_DUP_MAX (0x7fff) +#endif + + +/* POSIX `cflags' bits (i.e., information for `regcomp'). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* POSIX `eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + +/* Use PMATCH[0] to delimit the start and end of the search in the + buffer. */ +#define REG_STARTEND (1 << 2) + + +/* If any error codes are removed, changed, or added, update the + `re_error_msg' table in regex.c. */ +typedef enum +{ +#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K + REG_ENOSYS = -1, /* This will never happen for this implementation. */ +#endif + + REG_NOERROR = 0, /* Success. */ + REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + REG_BADPAT, /* Invalid pattern. */ + REG_ECOLLATE, /* Inalid collating element. */ + REG_ECTYPE, /* Invalid character class name. */ + REG_EESCAPE, /* Trailing backslash. */ + REG_ESUBREG, /* Invalid back reference. */ + REG_EBRACK, /* Unmatched left bracket. */ + REG_EPAREN, /* Parenthesis imbalance. */ + REG_EBRACE, /* Unmatched \{. */ + REG_BADBR, /* Invalid contents of \{\}. */ + REG_ERANGE, /* Invalid range end. */ + REG_ESPACE, /* Ran out of memory. */ + REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + REG_EEND, /* Premature end. */ + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields `buffer', `allocated', `fastmap', + `translate', and `no_sub' can be set. After the pattern has been + compiled, the `re_nsub' field is available. All other fields are + private to the regex routines. */ + +#ifndef RE_TRANSLATE_TYPE +# define __RE_TRANSLATE_TYPE unsigned char * +# ifdef __USE_GNU +# define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE +# endif +#endif + +#ifdef __USE_GNU +# define __REPB_PREFIX(name) name +#else +# define __REPB_PREFIX(name) __##name +#endif + +struct re_pattern_buffer +{ + /* Space that holds the compiled pattern. It is declared as + `unsigned char *' because its elements are sometimes used as + array indexes. */ + unsigned char *__REPB_PREFIX(buffer); + + /* Number of bytes to which `buffer' points. */ + unsigned long int __REPB_PREFIX(allocated); + + /* Number of bytes actually used in `buffer'. */ + unsigned long int __REPB_PREFIX(used); + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t __REPB_PREFIX(syntax); + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses the + fastmap, if there is one, to skip over impossible starting points + for matches. */ + char *__REPB_PREFIX(fastmap); + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation is + applied to a pattern when it is compiled and to a string when it + is matched. */ + __RE_TRANSLATE_TYPE __REPB_PREFIX(translate); + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Zero if this pattern cannot match the empty string, one else. + Well, in truth it's used only in `re_search_2', to see whether or + not we should use the fastmap, so we don't set this absolutely + perfectly; see `re_compile_fastmap' (the `duplicate' case). */ + unsigned __REPB_PREFIX(can_be_null) : 1; + + /* If REGS_UNALLOCATED, allocate space in the `regs' structure + for `max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#ifdef __USE_GNU +# define REGS_UNALLOCATED 0 +# define REGS_REALLOCATE 1 +# define REGS_FIXED 2 +#endif + unsigned __REPB_PREFIX(regs_allocated) : 2; + + /* Set to zero when `regex_compile' compiles a pattern; set to one + by `re_compile_fastmap' if it updates the fastmap. */ + unsigned __REPB_PREFIX(fastmap_accurate) : 1; + + /* If set, `re_match_2' does not return information about + subexpressions. */ + unsigned __REPB_PREFIX(no_sub) : 1; + + /* If set, a beginning-of-line anchor doesn't match at the beginning + of the string. */ + unsigned __REPB_PREFIX(not_bol) : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned __REPB_PREFIX(not_eol) : 1; + + /* If true, an anchor at a newline matches. */ + unsigned __REPB_PREFIX(newline_anchor) : 1; +}; + +typedef struct re_pattern_buffer regex_t; + +/* Type for byte offsets within the string. POSIX mandates this. */ +typedef int regoff_t; + + +#ifdef __USE_GNU +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + `re_match_2' returns information about at least this many registers + the first time a `regs' structure is passed. */ +# ifndef RE_NREGS +# define RE_NREGS 30 +# endif +#endif + + +/* POSIX specification for registers. Aside from the different names than + `re_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + +/* Declarations for routines. */ + +#ifdef __USE_GNU +/* Sets the current default syntax to SYNTAX, and return the old syntax. + You can also simply assign to the `re_syntax_options' variable. */ +extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global `re_syntax_options', into the buffer + BUFFER. Return NULL if successful, and an error string if not. */ +extern const char *re_compile_pattern (const char *__pattern, size_t __length, + struct re_pattern_buffer *__buffer); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int re_compile_fastmap (struct re_pattern_buffer *__buffer); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern int re_search (struct re_pattern_buffer *__buffer, const char *__cstring, + int __length, int __start, int __range, + struct re_registers *__regs); + + +/* Like `re_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern int re_search_2 (struct re_pattern_buffer *__buffer, + const char *__string1, int __length1, + const char *__string2, int __length2, int __start, + int __range, struct re_registers *__regs, int __stop); + + +/* Like `re_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern int re_match (struct re_pattern_buffer *__buffer, const char *__cstring, + int __length, int __start, struct re_registers *__regs); + + +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */ +extern int re_match_2 (struct re_pattern_buffer *__buffer, + const char *__string1, int __length1, + const char *__string2, int __length2, int __start, + struct re_registers *__regs, int __stop); + + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using BUFFER and REGS will use this memory + for recording register information. STARTS and ENDS must be + allocated with malloc, and must each be at least `NUM_REGS * sizeof + (regoff_t)' bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ +extern void re_set_registers (struct re_pattern_buffer *__buffer, + struct re_registers *__regs, + unsigned int __num_regs, + regoff_t *__starts, regoff_t *__ends); +#endif /* Use GNU */ + +#if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_BSD) +# ifndef _CRAY +/* 4.2 bsd compatibility. */ +extern char *re_comp (const char *); +extern int re_exec (const char *); +# endif +#endif + +/* GCC 2.95 and later have "__restrict"; C99 compilers have + "restrict", and "configure" may have defined "restrict". */ +#ifndef __restrict +# if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) +# if defined restrict || 199901L <= __STDC_VERSION__ +# define __restrict restrict +# else +# define __restrict +# endif +# endif +#endif +/* gcc 3.1 and up support the [restrict] syntax. */ +#ifndef __restrict_arr +# if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \ + && !defined __GNUG__ +# define __restrict_arr __restrict +# else +# define __restrict_arr +# endif +#endif + +/* POSIX compatibility. */ +extern int regcomp (regex_t *__restrict __preg, + const char *__restrict __pattern, + int __cflags); + +extern int regexec (const regex_t *__restrict __preg, + const char *__restrict __cstring, size_t __nmatch, + regmatch_t __pmatch[__restrict_arr], + int __eflags); + +extern size_t regerror (int __errcode, const regex_t *__restrict __preg, + char *__restrict __errbuf, size_t __errbuf_size); + +extern void regfree (regex_t *__preg); + + +#ifdef __cplusplus +} +#endif /* C++ */ + +#endif /* regex.h */ diff --git a/deps/regex/regex_internal.c b/deps/regex/regex_internal.c new file mode 100644 index 000000000..193854cf5 --- /dev/null +++ b/deps/regex/regex_internal.c @@ -0,0 +1,1744 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002-2006, 2010 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +static void re_string_construct_common (const char *str, int len, + re_string_t *pstr, + RE_TRANSLATE_TYPE trans, int icase, + const re_dfa_t *dfa) internal_function; +static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa, + const re_node_set *nodes, + unsigned int hash) internal_function; +static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa, + const re_node_set *nodes, + unsigned int context, + unsigned int hash) internal_function; + +#ifdef GAWK +#undef MAX /* safety */ +static int +MAX(size_t a, size_t b) +{ + return (a > b ? a : b); +} +#endif + +/* Functions for string operation. */ + +/* This function allocate the buffers. It is necessary to call + re_string_reconstruct before using the object. */ + +static reg_errcode_t +internal_function +re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len, + RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) +{ + reg_errcode_t ret; + int init_buf_len; + + /* Ensure at least one character fits into the buffers. */ + if (init_len < dfa->mb_cur_max) + init_len = dfa->mb_cur_max; + init_buf_len = (len + 1 < init_len) ? len + 1: init_len; + re_string_construct_common (str, len, pstr, trans, icase, dfa); + + ret = re_string_realloc_buffers (pstr, init_buf_len); + if (BE (ret != REG_NOERROR, 0)) + return ret; + + pstr->word_char = dfa->word_char; + pstr->word_ops_used = dfa->word_ops_used; + pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; + pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len; + pstr->valid_raw_len = pstr->valid_len; + return REG_NOERROR; +} + +/* This function allocate the buffers, and initialize them. */ + +static reg_errcode_t +internal_function +re_string_construct (re_string_t *pstr, const char *str, int len, + RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa) +{ + reg_errcode_t ret; + memset (pstr, '\0', sizeof (re_string_t)); + re_string_construct_common (str, len, pstr, trans, icase, dfa); + + if (len > 0) + { + ret = re_string_realloc_buffers (pstr, len + 1); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str; + + if (icase) + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + { + while (1) + { + ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + if (pstr->valid_raw_len >= len) + break; + if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max) + break; + ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + } + else +#endif /* RE_ENABLE_I18N */ + build_upper_buffer (pstr); + } + else + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + build_wcs_buffer (pstr); + else +#endif /* RE_ENABLE_I18N */ + { + if (trans != NULL) + re_string_translate_buffer (pstr); + else + { + pstr->valid_len = pstr->bufs_len; + pstr->valid_raw_len = pstr->bufs_len; + } + } + } + + return REG_NOERROR; +} + +/* Helper functions for re_string_allocate, and re_string_construct. */ + +static reg_errcode_t +internal_function +re_string_realloc_buffers (re_string_t *pstr, int new_buf_len) +{ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + wint_t *new_wcs; + + /* Avoid overflow in realloc. */ + const size_t max_object_size = MAX (sizeof (wint_t), sizeof (int)); + if (BE (SIZE_MAX / max_object_size < new_buf_len, 0)) + return REG_ESPACE; + + new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len); + if (BE (new_wcs == NULL, 0)) + return REG_ESPACE; + pstr->wcs = new_wcs; + if (pstr->offsets != NULL) + { + int *new_offsets = re_realloc (pstr->offsets, int, new_buf_len); + if (BE (new_offsets == NULL, 0)) + return REG_ESPACE; + pstr->offsets = new_offsets; + } + } +#endif /* RE_ENABLE_I18N */ + if (pstr->mbs_allocated) + { + unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char, + new_buf_len); + if (BE (new_mbs == NULL, 0)) + return REG_ESPACE; + pstr->mbs = new_mbs; + } + pstr->bufs_len = new_buf_len; + return REG_NOERROR; +} + + +static void +internal_function +re_string_construct_common (const char *str, int len, re_string_t *pstr, + RE_TRANSLATE_TYPE trans, int icase, + const re_dfa_t *dfa) +{ + pstr->raw_mbs = (const unsigned char *) str; + pstr->len = len; + pstr->raw_len = len; + pstr->trans = trans; + pstr->icase = icase ? 1 : 0; + pstr->mbs_allocated = (trans != NULL || icase); + pstr->mb_cur_max = dfa->mb_cur_max; + pstr->is_utf8 = dfa->is_utf8; + pstr->map_notascii = dfa->map_notascii; + pstr->stop = pstr->len; + pstr->raw_stop = pstr->stop; +} + +#ifdef RE_ENABLE_I18N + +/* Build wide character buffer PSTR->WCS. + If the byte sequence of the string are: + (0), (1), (0), (1), + Then wide character buffer will be: + , WEOF , , WEOF , + We use WEOF for padding, they indicate that the position isn't + a first byte of a multibyte character. + + Note that this function assumes PSTR->VALID_LEN elements are already + built and starts from PSTR->VALID_LEN. */ + +static void +internal_function +build_wcs_buffer (re_string_t *pstr) +{ +#ifdef _LIBC + unsigned char buf[MB_LEN_MAX]; + assert (MB_LEN_MAX >= pstr->mb_cur_max); +#else + unsigned char buf[64]; +#endif + mbstate_t prev_st; + int byte_idx, end_idx, remain_len; + size_t mbclen; + + /* Build the buffers from pstr->valid_len to either pstr->len or + pstr->bufs_len. */ + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + for (byte_idx = pstr->valid_len; byte_idx < end_idx;) + { + wchar_t wc; + const char *p; + + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + /* Apply the translation if we need. */ + if (BE (pstr->trans != NULL, 0)) + { + int i, ch; + + for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) + { + ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i]; + buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch]; + } + p = (const char *) buf; + } + else + p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx; + mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state); + if (BE (mbclen == (size_t) -2, 0)) + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0)) + { + /* We treat these cases as a singlebyte character. */ + mbclen = 1; + wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; + if (BE (pstr->trans != NULL, 0)) + wc = pstr->trans[wc]; + pstr->cur_state = prev_st; + } + + /* Write wide character and padding. */ + pstr->wcs[byte_idx++] = wc; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = byte_idx; +} + +/* Build wide character buffer PSTR->WCS like build_wcs_buffer, + but for REG_ICASE. */ + +static reg_errcode_t +internal_function +build_wcs_upper_buffer (re_string_t *pstr) +{ + mbstate_t prev_st; + int src_idx, byte_idx, end_idx, remain_len; + size_t mbclen; +#ifdef _LIBC + char buf[MB_LEN_MAX]; + assert (MB_LEN_MAX >= pstr->mb_cur_max); +#else + char buf[64]; +#endif + + byte_idx = pstr->valid_len; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + /* The following optimization assumes that ASCII characters can be + mapped to wide characters with a simple cast. */ + if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed) + { + while (byte_idx < end_idx) + { + wchar_t wc; + + if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]) + && mbsinit (&pstr->cur_state)) + { + /* In case of a singlebyte character. */ + pstr->mbs[byte_idx] + = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]); + /* The next step uses the assumption that wchar_t is encoded + ASCII-safe: all ASCII values can be converted like this. */ + pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx]; + ++byte_idx; + continue; + } + + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + mbclen = __mbrtowc (&wc, + ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx + + byte_idx), remain_len, &pstr->cur_state); + if (BE (mbclen + 2 > 2, 1)) + { + wchar_t wcu = wc; + if (iswlower (wc)) + { + size_t mbcdlen; + + wcu = towupper (wc); + mbcdlen = wcrtomb (buf, wcu, &prev_st); + if (BE (mbclen == mbcdlen, 1)) + memcpy (pstr->mbs + byte_idx, buf, mbclen); + else + { + src_idx = byte_idx; + goto offsets_needed; + } + } + else + memcpy (pstr->mbs + byte_idx, + pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen); + pstr->wcs[byte_idx++] = wcu; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + else if (mbclen == (size_t) -1 || mbclen == 0) + { + /* It is an invalid character or '\0'. Just use the byte. */ + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]; + pstr->mbs[byte_idx] = ch; + /* And also cast it to wide char. */ + pstr->wcs[byte_idx++] = (wchar_t) ch; + if (BE (mbclen == (size_t) -1, 0)) + pstr->cur_state = prev_st; + } + else + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = byte_idx; + return REG_NOERROR; + } + else + for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;) + { + wchar_t wc; + const char *p; + offsets_needed: + remain_len = end_idx - byte_idx; + prev_st = pstr->cur_state; + if (BE (pstr->trans != NULL, 0)) + { + int i, ch; + + for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i) + { + ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i]; + buf[i] = pstr->trans[ch]; + } + p = (const char *) buf; + } + else + p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx; + mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state); + if (BE (mbclen + 2 > 2, 1)) + { + wchar_t wcu = wc; + if (iswlower (wc)) + { + size_t mbcdlen; + + wcu = towupper (wc); + mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st); + if (BE (mbclen == mbcdlen, 1)) + memcpy (pstr->mbs + byte_idx, buf, mbclen); + else if (mbcdlen != (size_t) -1) + { + size_t i; + + if (byte_idx + mbcdlen > pstr->bufs_len) + { + pstr->cur_state = prev_st; + break; + } + + if (pstr->offsets == NULL) + { + pstr->offsets = re_malloc (int, pstr->bufs_len); + + if (pstr->offsets == NULL) + return REG_ESPACE; + } + if (!pstr->offsets_needed) + { + for (i = 0; i < (size_t) byte_idx; ++i) + pstr->offsets[i] = i; + pstr->offsets_needed = 1; + } + + memcpy (pstr->mbs + byte_idx, buf, mbcdlen); + pstr->wcs[byte_idx] = wcu; + pstr->offsets[byte_idx] = src_idx; + for (i = 1; i < mbcdlen; ++i) + { + pstr->offsets[byte_idx + i] + = src_idx + (i < mbclen ? i : mbclen - 1); + pstr->wcs[byte_idx + i] = WEOF; + } + pstr->len += mbcdlen - mbclen; + if (pstr->raw_stop > src_idx) + pstr->stop += mbcdlen - mbclen; + end_idx = (pstr->bufs_len > pstr->len) + ? pstr->len : pstr->bufs_len; + byte_idx += mbcdlen; + src_idx += mbclen; + continue; + } + else + memcpy (pstr->mbs + byte_idx, p, mbclen); + } + else + memcpy (pstr->mbs + byte_idx, p, mbclen); + + if (BE (pstr->offsets_needed != 0, 0)) + { + size_t i; + for (i = 0; i < mbclen; ++i) + pstr->offsets[byte_idx + i] = src_idx + i; + } + src_idx += mbclen; + + pstr->wcs[byte_idx++] = wcu; + /* Write paddings. */ + for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;) + pstr->wcs[byte_idx++] = WEOF; + } + else if (mbclen == (size_t) -1 || mbclen == 0) + { + /* It is an invalid character or '\0'. Just use the byte. */ + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx]; + + if (BE (pstr->trans != NULL, 0)) + ch = pstr->trans [ch]; + pstr->mbs[byte_idx] = ch; + + if (BE (pstr->offsets_needed != 0, 0)) + pstr->offsets[byte_idx] = src_idx; + ++src_idx; + + /* And also cast it to wide char. */ + pstr->wcs[byte_idx++] = (wchar_t) ch; + if (BE (mbclen == (size_t) -1, 0)) + pstr->cur_state = prev_st; + } + else + { + /* The buffer doesn't have enough space, finish to build. */ + pstr->cur_state = prev_st; + break; + } + } + pstr->valid_len = byte_idx; + pstr->valid_raw_len = src_idx; + return REG_NOERROR; +} + +/* Skip characters until the index becomes greater than NEW_RAW_IDX. + Return the index. */ + +static int +internal_function +re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc) +{ + mbstate_t prev_st; + int rawbuf_idx; + size_t mbclen; + wint_t wc = WEOF; + + /* Skip the characters which are not necessary to check. */ + for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len; + rawbuf_idx < new_raw_idx;) + { + wchar_t wc2; + int remain_len = pstr->len - rawbuf_idx; + prev_st = pstr->cur_state; + mbclen = __mbrtowc (&wc2, (const char *) pstr->raw_mbs + rawbuf_idx, + remain_len, &pstr->cur_state); + if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0)) + { + /* We treat these cases as a single byte character. */ + if (mbclen == 0 || remain_len == 0) + wc = L'\0'; + else + wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx); + mbclen = 1; + pstr->cur_state = prev_st; + } + else + wc = (wint_t) wc2; + /* Then proceed the next character. */ + rawbuf_idx += mbclen; + } + *last_wc = (wint_t) wc; + return rawbuf_idx; +} +#endif /* RE_ENABLE_I18N */ + +/* Build the buffer PSTR->MBS, and apply the translation if we need. + This function is used in case of REG_ICASE. */ + +static void +internal_function +build_upper_buffer (re_string_t *pstr) +{ + int char_idx, end_idx; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx) + { + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx]; + if (BE (pstr->trans != NULL, 0)) + ch = pstr->trans[ch]; + if (islower (ch)) + pstr->mbs[char_idx] = toupper (ch); + else + pstr->mbs[char_idx] = ch; + } + pstr->valid_len = char_idx; + pstr->valid_raw_len = char_idx; +} + +/* Apply TRANS to the buffer in PSTR. */ + +static void +internal_function +re_string_translate_buffer (re_string_t *pstr) +{ + int buf_idx, end_idx; + end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len; + + for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx) + { + int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx]; + pstr->mbs[buf_idx] = pstr->trans[ch]; + } + + pstr->valid_len = buf_idx; + pstr->valid_raw_len = buf_idx; +} + +/* This function re-construct the buffers. + Concretely, convert to wide character in case of pstr->mb_cur_max > 1, + convert to upper case in case of REG_ICASE, apply translation. */ + +static reg_errcode_t +internal_function +re_string_reconstruct (re_string_t *pstr, int idx, int eflags) +{ + int offset = idx - pstr->raw_mbs_idx; + if (BE (offset < 0, 0)) + { + /* Reset buffer. */ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); +#endif /* RE_ENABLE_I18N */ + pstr->len = pstr->raw_len; + pstr->stop = pstr->raw_stop; + pstr->valid_len = 0; + pstr->raw_mbs_idx = 0; + pstr->valid_raw_len = 0; + pstr->offsets_needed = 0; + pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF + : CONTEXT_NEWLINE | CONTEXT_BEGBUF); + if (!pstr->mbs_allocated) + pstr->mbs = (unsigned char *) pstr->raw_mbs; + offset = idx; + } + + if (BE (offset != 0, 1)) + { + /* Should the already checked characters be kept? */ + if (BE (offset < pstr->valid_raw_len, 1)) + { + /* Yes, move them to the front of the buffer. */ +#ifdef RE_ENABLE_I18N + if (BE (pstr->offsets_needed, 0)) + { + int low = 0, high = pstr->valid_len, mid; + do + { + mid = (high + low) / 2; + if (pstr->offsets[mid] > offset) + high = mid; + else if (pstr->offsets[mid] < offset) + low = mid + 1; + else + break; + } + while (low < high); + if (pstr->offsets[mid] < offset) + ++mid; + pstr->tip_context = re_string_context_at (pstr, mid - 1, + eflags); + /* This can be quite complicated, so handle specially + only the common and easy case where the character with + different length representation of lower and upper + case is present at or after offset. */ + if (pstr->valid_len > offset + && mid == offset && pstr->offsets[mid] == offset) + { + memmove (pstr->wcs, pstr->wcs + offset, + (pstr->valid_len - offset) * sizeof (wint_t)); + memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset); + pstr->valid_len -= offset; + pstr->valid_raw_len -= offset; + for (low = 0; low < pstr->valid_len; low++) + pstr->offsets[low] = pstr->offsets[low + offset] - offset; + } + else + { + /* Otherwise, just find out how long the partial multibyte + character at offset is and fill it with WEOF/255. */ + pstr->len = pstr->raw_len - idx + offset; + pstr->stop = pstr->raw_stop - idx + offset; + pstr->offsets_needed = 0; + while (mid > 0 && pstr->offsets[mid - 1] == offset) + --mid; + while (mid < pstr->valid_len) + if (pstr->wcs[mid] != WEOF) + break; + else + ++mid; + if (mid == pstr->valid_len) + pstr->valid_len = 0; + else + { + pstr->valid_len = pstr->offsets[mid] - offset; + if (pstr->valid_len) + { + for (low = 0; low < pstr->valid_len; ++low) + pstr->wcs[low] = WEOF; + memset (pstr->mbs, 255, pstr->valid_len); + } + } + pstr->valid_raw_len = pstr->valid_len; + } + } + else +#endif + { + pstr->tip_context = re_string_context_at (pstr, offset - 1, + eflags); +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + memmove (pstr->wcs, pstr->wcs + offset, + (pstr->valid_len - offset) * sizeof (wint_t)); +#endif /* RE_ENABLE_I18N */ + if (BE (pstr->mbs_allocated, 0)) + memmove (pstr->mbs, pstr->mbs + offset, + pstr->valid_len - offset); + pstr->valid_len -= offset; + pstr->valid_raw_len -= offset; +#if DEBUG + assert (pstr->valid_len > 0); +#endif + } + } + else + { +#ifdef RE_ENABLE_I18N + /* No, skip all characters until IDX. */ + int prev_valid_len = pstr->valid_len; + + if (BE (pstr->offsets_needed, 0)) + { + pstr->len = pstr->raw_len - idx + offset; + pstr->stop = pstr->raw_stop - idx + offset; + pstr->offsets_needed = 0; + } +#endif + pstr->valid_len = 0; +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + int wcs_idx; + wint_t wc = WEOF; + + if (pstr->is_utf8) + { + const unsigned char *raw, *p, *end; + + /* Special case UTF-8. Multi-byte chars start with any + byte other than 0x80 - 0xbf. */ + raw = pstr->raw_mbs + pstr->raw_mbs_idx; + end = raw + (offset - pstr->mb_cur_max); + if (end < pstr->raw_mbs) + end = pstr->raw_mbs; + p = raw + offset - 1; +#ifdef _LIBC + /* We know the wchar_t encoding is UCS4, so for the simple + case, ASCII characters, skip the conversion step. */ + if (isascii (*p) && BE (pstr->trans == NULL, 1)) + { + memset (&pstr->cur_state, '\0', sizeof (mbstate_t)); + /* pstr->valid_len = 0; */ + wc = (wchar_t) *p; + } + else +#endif + for (; p >= end; --p) + if ((*p & 0xc0) != 0x80) + { + mbstate_t cur_state; + wchar_t wc2; + int mlen = raw + pstr->len - p; + unsigned char buf[6]; + size_t mbclen; + + if (BE (pstr->trans != NULL, 0)) + { + int i = mlen < 6 ? mlen : 6; + while (--i >= 0) + buf[i] = pstr->trans[p[i]]; + } + /* XXX Don't use mbrtowc, we know which conversion + to use (UTF-8 -> UCS4). */ + memset (&cur_state, 0, sizeof (cur_state)); + mbclen = __mbrtowc (&wc2, (const char *) p, mlen, + &cur_state); + if (raw + offset - p <= mbclen + && mbclen < (size_t) -2) + { + memset (&pstr->cur_state, '\0', + sizeof (mbstate_t)); + pstr->valid_len = mbclen - (raw + offset - p); + wc = wc2; + } + break; + } + } + + if (wc == WEOF) + pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx; + if (wc == WEOF) + pstr->tip_context + = re_string_context_at (pstr, prev_valid_len - 1, eflags); + else + pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0) + && IS_WIDE_WORD_CHAR (wc)) + ? CONTEXT_WORD + : ((IS_WIDE_NEWLINE (wc) + && pstr->newline_anchor) + ? CONTEXT_NEWLINE : 0)); + if (BE (pstr->valid_len, 0)) + { + for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx) + pstr->wcs[wcs_idx] = WEOF; + if (pstr->mbs_allocated) + memset (pstr->mbs, 255, pstr->valid_len); + } + pstr->valid_raw_len = pstr->valid_len; + } + else +#endif /* RE_ENABLE_I18N */ + { + int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1]; + pstr->valid_raw_len = 0; + if (pstr->trans) + c = pstr->trans[c]; + pstr->tip_context = (bitset_contain (pstr->word_char, c) + ? CONTEXT_WORD + : ((IS_NEWLINE (c) && pstr->newline_anchor) + ? CONTEXT_NEWLINE : 0)); + } + } + if (!BE (pstr->mbs_allocated, 0)) + pstr->mbs += offset; + } + pstr->raw_mbs_idx = idx; + pstr->len -= offset; + pstr->stop -= offset; + + /* Then build the buffers. */ +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + if (pstr->icase) + { + reg_errcode_t ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + else + build_wcs_buffer (pstr); + } + else +#endif /* RE_ENABLE_I18N */ + if (BE (pstr->mbs_allocated, 0)) + { + if (pstr->icase) + build_upper_buffer (pstr); + else if (pstr->trans != NULL) + re_string_translate_buffer (pstr); + } + else + pstr->valid_len = pstr->len; + + pstr->cur_idx = 0; + return REG_NOERROR; +} + +static unsigned char +internal_function __attribute ((pure)) +re_string_peek_byte_case (const re_string_t *pstr, int idx) +{ + int ch, off; + + /* Handle the common (easiest) cases first. */ + if (BE (!pstr->mbs_allocated, 1)) + return re_string_peek_byte (pstr, idx); + +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1 + && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx)) + return re_string_peek_byte (pstr, idx); +#endif + + off = pstr->cur_idx + idx; +#ifdef RE_ENABLE_I18N + if (pstr->offsets_needed) + off = pstr->offsets[off]; +#endif + + ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; + +#ifdef RE_ENABLE_I18N + /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I + this function returns CAPITAL LETTER I instead of first byte of + DOTLESS SMALL LETTER I. The latter would confuse the parser, + since peek_byte_case doesn't advance cur_idx in any way. */ + if (pstr->offsets_needed && !isascii (ch)) + return re_string_peek_byte (pstr, idx); +#endif + + return ch; +} + +static unsigned char +internal_function __attribute ((pure)) +re_string_fetch_byte_case (re_string_t *pstr) +{ + if (BE (!pstr->mbs_allocated, 1)) + return re_string_fetch_byte (pstr); + +#ifdef RE_ENABLE_I18N + if (pstr->offsets_needed) + { + int off, ch; + + /* For tr_TR.UTF-8 [[:islower:]] there is + [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip + in that case the whole multi-byte character and return + the original letter. On the other side, with + [[: DOTLESS SMALL LETTER I return [[:I, as doing + anything else would complicate things too much. */ + + if (!re_string_first_byte (pstr, pstr->cur_idx)) + return re_string_fetch_byte (pstr); + + off = pstr->offsets[pstr->cur_idx]; + ch = pstr->raw_mbs[pstr->raw_mbs_idx + off]; + + if (! isascii (ch)) + return re_string_fetch_byte (pstr); + + re_string_skip_bytes (pstr, + re_string_char_size_at (pstr, pstr->cur_idx)); + return ch; + } +#endif + + return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++]; +} + +static void +internal_function +re_string_destruct (re_string_t *pstr) +{ +#ifdef RE_ENABLE_I18N + re_free (pstr->wcs); + re_free (pstr->offsets); +#endif /* RE_ENABLE_I18N */ + if (pstr->mbs_allocated) + re_free (pstr->mbs); +} + +/* Return the context at IDX in INPUT. */ + +static unsigned int +internal_function +re_string_context_at (const re_string_t *input, int idx, int eflags) +{ + int c; + if (BE (idx < 0, 0)) + /* In this case, we use the value stored in input->tip_context, + since we can't know the character in input->mbs[-1] here. */ + return input->tip_context; + if (BE (idx == input->len, 0)) + return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF + : CONTEXT_NEWLINE | CONTEXT_ENDBUF); +#ifdef RE_ENABLE_I18N + if (input->mb_cur_max > 1) + { + wint_t wc; + int wc_idx = idx; + while(input->wcs[wc_idx] == WEOF) + { +#ifdef DEBUG + /* It must not happen. */ + assert (wc_idx >= 0); +#endif + --wc_idx; + if (wc_idx < 0) + return input->tip_context; + } + wc = input->wcs[wc_idx]; + if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc)) + return CONTEXT_WORD; + return (IS_WIDE_NEWLINE (wc) && input->newline_anchor + ? CONTEXT_NEWLINE : 0); + } + else +#endif + { + c = re_string_byte_at (input, idx); + if (bitset_contain (input->word_char, c)) + return CONTEXT_WORD; + return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0; + } +} + +/* Functions for set operation. */ + +static reg_errcode_t +internal_function +re_node_set_alloc (re_node_set *set, int size) +{ + /* + * ADR: valgrind says size can be 0, which then doesn't + * free the block of size 0. Harumph. This seems + * to work ok, though. + */ + if (size == 0) + { + memset(set, 0, sizeof(*set)); + return REG_NOERROR; + } + set->alloc = size; + set->nelem = 0; + set->elems = re_malloc (int, size); + if (BE (set->elems == NULL, 0)) + return REG_ESPACE; + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +re_node_set_init_1 (re_node_set *set, int elem) +{ + set->alloc = 1; + set->nelem = 1; + set->elems = re_malloc (int, 1); + if (BE (set->elems == NULL, 0)) + { + set->alloc = set->nelem = 0; + return REG_ESPACE; + } + set->elems[0] = elem; + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +re_node_set_init_2 (re_node_set *set, int elem1, int elem2) +{ + set->alloc = 2; + set->elems = re_malloc (int, 2); + if (BE (set->elems == NULL, 0)) + return REG_ESPACE; + if (elem1 == elem2) + { + set->nelem = 1; + set->elems[0] = elem1; + } + else + { + set->nelem = 2; + if (elem1 < elem2) + { + set->elems[0] = elem1; + set->elems[1] = elem2; + } + else + { + set->elems[0] = elem2; + set->elems[1] = elem1; + } + } + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +re_node_set_init_copy (re_node_set *dest, const re_node_set *src) +{ + dest->nelem = src->nelem; + if (src->nelem > 0) + { + dest->alloc = dest->nelem; + dest->elems = re_malloc (int, dest->alloc); + if (BE (dest->elems == NULL, 0)) + { + dest->alloc = dest->nelem = 0; + return REG_ESPACE; + } + memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); + } + else + re_node_set_init_empty (dest); + return REG_NOERROR; +} + +/* Calculate the intersection of the sets SRC1 and SRC2. And merge it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. + Note: We assume dest->elems is NULL, when dest->alloc is 0. */ + +static reg_errcode_t +internal_function +re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1, + const re_node_set *src2) +{ + int i1, i2, is, id, delta, sbase; + if (src1->nelem == 0 || src2->nelem == 0) + return REG_NOERROR; + + /* We need dest->nelem + 2 * elems_in_intersection; this is a + conservative estimate. */ + if (src1->nelem + src2->nelem + dest->nelem > dest->alloc) + { + int new_alloc = src1->nelem + src2->nelem + dest->alloc; + int *new_elems = re_realloc (dest->elems, int, new_alloc); + if (BE (new_elems == NULL, 0)) + return REG_ESPACE; + dest->elems = new_elems; + dest->alloc = new_alloc; + } + + /* Find the items in the intersection of SRC1 and SRC2, and copy + into the top of DEST those that are not already in DEST itself. */ + sbase = dest->nelem + src1->nelem + src2->nelem; + i1 = src1->nelem - 1; + i2 = src2->nelem - 1; + id = dest->nelem - 1; + for (;;) + { + if (src1->elems[i1] == src2->elems[i2]) + { + /* Try to find the item in DEST. Maybe we could binary search? */ + while (id >= 0 && dest->elems[id] > src1->elems[i1]) + --id; + + if (id < 0 || dest->elems[id] != src1->elems[i1]) + dest->elems[--sbase] = src1->elems[i1]; + + if (--i1 < 0 || --i2 < 0) + break; + } + + /* Lower the highest of the two items. */ + else if (src1->elems[i1] < src2->elems[i2]) + { + if (--i2 < 0) + break; + } + else + { + if (--i1 < 0) + break; + } + } + + id = dest->nelem - 1; + is = dest->nelem + src1->nelem + src2->nelem - 1; + delta = is - sbase + 1; + + /* Now copy. When DELTA becomes zero, the remaining + DEST elements are already in place; this is more or + less the same loop that is in re_node_set_merge. */ + dest->nelem += delta; + if (delta > 0 && id >= 0) + for (;;) + { + if (dest->elems[is] > dest->elems[id]) + { + /* Copy from the top. */ + dest->elems[id + delta--] = dest->elems[is--]; + if (delta == 0) + break; + } + else + { + /* Slide from the bottom. */ + dest->elems[id + delta] = dest->elems[id]; + if (--id < 0) + break; + } + } + + /* Copy remaining SRC elements. */ + memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int)); + + return REG_NOERROR; +} + +/* Calculate the union set of the sets SRC1 and SRC2. And store it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ + +static reg_errcode_t +internal_function +re_node_set_init_union (re_node_set *dest, const re_node_set *src1, + const re_node_set *src2) +{ + int i1, i2, id; + if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0) + { + dest->alloc = src1->nelem + src2->nelem; + dest->elems = re_malloc (int, dest->alloc); + if (BE (dest->elems == NULL, 0)) + return REG_ESPACE; + } + else + { + if (src1 != NULL && src1->nelem > 0) + return re_node_set_init_copy (dest, src1); + else if (src2 != NULL && src2->nelem > 0) + return re_node_set_init_copy (dest, src2); + else + re_node_set_init_empty (dest); + return REG_NOERROR; + } + for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;) + { + if (src1->elems[i1] > src2->elems[i2]) + { + dest->elems[id++] = src2->elems[i2++]; + continue; + } + if (src1->elems[i1] == src2->elems[i2]) + ++i2; + dest->elems[id++] = src1->elems[i1++]; + } + if (i1 < src1->nelem) + { + memcpy (dest->elems + id, src1->elems + i1, + (src1->nelem - i1) * sizeof (int)); + id += src1->nelem - i1; + } + else if (i2 < src2->nelem) + { + memcpy (dest->elems + id, src2->elems + i2, + (src2->nelem - i2) * sizeof (int)); + id += src2->nelem - i2; + } + dest->nelem = id; + return REG_NOERROR; +} + +/* Calculate the union set of the sets DEST and SRC. And store it to + DEST. Return value indicate the error code or REG_NOERROR if succeeded. */ + +static reg_errcode_t +internal_function +re_node_set_merge (re_node_set *dest, const re_node_set *src) +{ + int is, id, sbase, delta; + if (src == NULL || src->nelem == 0) + return REG_NOERROR; + if (dest->alloc < 2 * src->nelem + dest->nelem) + { + int new_alloc = 2 * (src->nelem + dest->alloc); + int *new_buffer = re_realloc (dest->elems, int, new_alloc); + if (BE (new_buffer == NULL, 0)) + return REG_ESPACE; + dest->elems = new_buffer; + dest->alloc = new_alloc; + } + + if (BE (dest->nelem == 0, 0)) + { + dest->nelem = src->nelem; + memcpy (dest->elems, src->elems, src->nelem * sizeof (int)); + return REG_NOERROR; + } + + /* Copy into the top of DEST the items of SRC that are not + found in DEST. Maybe we could binary search in DEST? */ + for (sbase = dest->nelem + 2 * src->nelem, + is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) + { + if (dest->elems[id] == src->elems[is]) + is--, id--; + else if (dest->elems[id] < src->elems[is]) + dest->elems[--sbase] = src->elems[is--]; + else /* if (dest->elems[id] > src->elems[is]) */ + --id; + } + + if (is >= 0) + { + /* If DEST is exhausted, the remaining items of SRC must be unique. */ + sbase -= is + 1; + memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int)); + } + + id = dest->nelem - 1; + is = dest->nelem + 2 * src->nelem - 1; + delta = is - sbase + 1; + if (delta == 0) + return REG_NOERROR; + + /* Now copy. When DELTA becomes zero, the remaining + DEST elements are already in place. */ + dest->nelem += delta; + for (;;) + { + if (dest->elems[is] > dest->elems[id]) + { + /* Copy from the top. */ + dest->elems[id + delta--] = dest->elems[is--]; + if (delta == 0) + break; + } + else + { + /* Slide from the bottom. */ + dest->elems[id + delta] = dest->elems[id]; + if (--id < 0) + { + /* Copy remaining SRC elements. */ + memcpy (dest->elems, dest->elems + sbase, + delta * sizeof (int)); + break; + } + } + } + + return REG_NOERROR; +} + +/* Insert the new element ELEM to the re_node_set* SET. + SET should not already have ELEM. + return -1 if an error is occured, return 1 otherwise. */ + +static int +internal_function +re_node_set_insert (re_node_set *set, int elem) +{ + int idx; + /* In case the set is empty. */ + if (set->alloc == 0) + { + if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1)) + return 1; + else + return -1; + } + + if (BE (set->nelem, 0) == 0) + { + /* We already guaranteed above that set->alloc != 0. */ + set->elems[0] = elem; + ++set->nelem; + return 1; + } + + /* Realloc if we need. */ + if (set->alloc == set->nelem) + { + int *new_elems; + set->alloc = set->alloc * 2; + new_elems = re_realloc (set->elems, int, set->alloc); + if (BE (new_elems == NULL, 0)) + return -1; + set->elems = new_elems; + } + + /* Move the elements which follows the new element. Test the + first element separately to skip a check in the inner loop. */ + if (elem < set->elems[0]) + { + idx = 0; + for (idx = set->nelem; idx > 0; idx--) + set->elems[idx] = set->elems[idx - 1]; + } + else + { + for (idx = set->nelem; set->elems[idx - 1] > elem; idx--) + set->elems[idx] = set->elems[idx - 1]; + } + + /* Insert the new element. */ + set->elems[idx] = elem; + ++set->nelem; + return 1; +} + +/* Insert the new element ELEM to the re_node_set* SET. + SET should not already have any element greater than or equal to ELEM. + Return -1 if an error is occured, return 1 otherwise. */ + +static int +internal_function +re_node_set_insert_last (re_node_set *set, int elem) +{ + /* Realloc if we need. */ + if (set->alloc == set->nelem) + { + int *new_elems; + set->alloc = (set->alloc + 1) * 2; + new_elems = re_realloc (set->elems, int, set->alloc); + if (BE (new_elems == NULL, 0)) + return -1; + set->elems = new_elems; + } + + /* Insert the new element. */ + set->elems[set->nelem++] = elem; + return 1; +} + +/* Compare two node sets SET1 and SET2. + return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */ + +static int +internal_function __attribute ((pure)) +re_node_set_compare (const re_node_set *set1, const re_node_set *set2) +{ + int i; + if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem) + return 0; + for (i = set1->nelem ; --i >= 0 ; ) + if (set1->elems[i] != set2->elems[i]) + return 0; + return 1; +} + +/* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */ + +static int +internal_function __attribute ((pure)) +re_node_set_contains (const re_node_set *set, int elem) +{ + unsigned int idx, right, mid; + if (set->nelem <= 0) + return 0; + + /* Binary search the element. */ + idx = 0; + right = set->nelem - 1; + while (idx < right) + { + mid = (idx + right) / 2; + if (set->elems[mid] < elem) + idx = mid + 1; + else + right = mid; + } + return set->elems[idx] == elem ? idx + 1 : 0; +} + +static void +internal_function +re_node_set_remove_at (re_node_set *set, int idx) +{ + if (idx < 0 || idx >= set->nelem) + return; + --set->nelem; + for (; idx < set->nelem; idx++) + set->elems[idx] = set->elems[idx + 1]; +} + + +/* Add the token TOKEN to dfa->nodes, and return the index of the token. + Or return -1, if an error will be occured. */ + +static int +internal_function +re_dfa_add_node (re_dfa_t *dfa, re_token_t token) +{ + if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0)) + { + size_t new_nodes_alloc = dfa->nodes_alloc * 2; + int *new_nexts, *new_indices; + re_node_set *new_edests, *new_eclosures; + re_token_t *new_nodes; + + /* Avoid overflows in realloc. */ + const size_t max_object_size = MAX (sizeof (re_token_t), + MAX (sizeof (re_node_set), + sizeof (int))); + if (BE (SIZE_MAX / max_object_size < new_nodes_alloc, 0)) + return -1; + + new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc); + if (BE (new_nodes == NULL, 0)) + return -1; + dfa->nodes = new_nodes; + new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc); + new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc); + new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc); + new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc); + if (BE (new_nexts == NULL || new_indices == NULL + || new_edests == NULL || new_eclosures == NULL, 0)) + return -1; + dfa->nexts = new_nexts; + dfa->org_indices = new_indices; + dfa->edests = new_edests; + dfa->eclosures = new_eclosures; + dfa->nodes_alloc = new_nodes_alloc; + } + dfa->nodes[dfa->nodes_len] = token; + dfa->nodes[dfa->nodes_len].constraint = 0; +#ifdef RE_ENABLE_I18N + dfa->nodes[dfa->nodes_len].accept_mb = + (token.type == OP_PERIOD && dfa->mb_cur_max > 1) || token.type == COMPLEX_BRACKET; +#endif + dfa->nexts[dfa->nodes_len] = -1; + re_node_set_init_empty (dfa->edests + dfa->nodes_len); + re_node_set_init_empty (dfa->eclosures + dfa->nodes_len); + return dfa->nodes_len++; +} + +static inline unsigned int +internal_function +calc_state_hash (const re_node_set *nodes, unsigned int context) +{ + unsigned int hash = nodes->nelem + context; + int i; + for (i = 0 ; i < nodes->nelem ; i++) + hash += nodes->elems[i]; + return hash; +} + +/* Search for the state whose node_set is equivalent to NODES. + Return the pointer to the state, if we found it in the DFA. + Otherwise create the new one and return it. In case of an error + return NULL and set the error code in ERR. + Note: - We assume NULL as the invalid state, then it is possible that + return value is NULL and ERR is REG_NOERROR. + - We never return non-NULL value in case of any errors, it is for + optimization. */ + +static re_dfastate_t * +internal_function +re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa, + const re_node_set *nodes) +{ + unsigned int hash; + re_dfastate_t *new_state; + struct re_state_table_entry *spot; + int i; + if (BE (nodes->nelem == 0, 0)) + { + *err = REG_NOERROR; + return NULL; + } + hash = calc_state_hash (nodes, 0); + spot = dfa->state_table + (hash & dfa->state_hash_mask); + + for (i = 0 ; i < spot->num ; i++) + { + re_dfastate_t *state = spot->array[i]; + if (hash != state->hash) + continue; + if (re_node_set_compare (&state->nodes, nodes)) + return state; + } + + /* There are no appropriate state in the dfa, create the new one. */ + new_state = create_ci_newstate (dfa, nodes, hash); + if (BE (new_state == NULL, 0)) + *err = REG_ESPACE; + + return new_state; +} + +/* Search for the state whose node_set is equivalent to NODES and + whose context is equivalent to CONTEXT. + Return the pointer to the state, if we found it in the DFA. + Otherwise create the new one and return it. In case of an error + return NULL and set the error code in ERR. + Note: - We assume NULL as the invalid state, then it is possible that + return value is NULL and ERR is REG_NOERROR. + - We never return non-NULL value in case of any errors, it is for + optimization. */ + +static re_dfastate_t * +internal_function +re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa, + const re_node_set *nodes, unsigned int context) +{ + unsigned int hash; + re_dfastate_t *new_state; + struct re_state_table_entry *spot; + int i; + if (nodes->nelem == 0) + { + *err = REG_NOERROR; + return NULL; + } + hash = calc_state_hash (nodes, context); + spot = dfa->state_table + (hash & dfa->state_hash_mask); + + for (i = 0 ; i < spot->num ; i++) + { + re_dfastate_t *state = spot->array[i]; + if (state->hash == hash + && state->context == context + && re_node_set_compare (state->entrance_nodes, nodes)) + return state; + } + /* There are no appropriate state in `dfa', create the new one. */ + new_state = create_cd_newstate (dfa, nodes, context, hash); + if (BE (new_state == NULL, 0)) + *err = REG_ESPACE; + + return new_state; +} + +/* Finish initialization of the new state NEWSTATE, and using its hash value + HASH put in the appropriate bucket of DFA's state table. Return value + indicates the error code if failed. */ + +static reg_errcode_t +register_state (const re_dfa_t *dfa, re_dfastate_t *newstate, + unsigned int hash) +{ + struct re_state_table_entry *spot; + reg_errcode_t err; + int i; + + newstate->hash = hash; + err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem); + if (BE (err != REG_NOERROR, 0)) + return REG_ESPACE; + for (i = 0; i < newstate->nodes.nelem; i++) + { + int elem = newstate->nodes.elems[i]; + if (!IS_EPSILON_NODE (dfa->nodes[elem].type)) + if (re_node_set_insert_last (&newstate->non_eps_nodes, elem) < 0) + return REG_ESPACE; + } + + spot = dfa->state_table + (hash & dfa->state_hash_mask); + if (BE (spot->alloc <= spot->num, 0)) + { + int new_alloc = 2 * spot->num + 2; + re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *, + new_alloc); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + spot->array = new_array; + spot->alloc = new_alloc; + } + spot->array[spot->num++] = newstate; + return REG_NOERROR; +} + +static void +free_state (re_dfastate_t *state) +{ + re_node_set_free (&state->non_eps_nodes); + re_node_set_free (&state->inveclosure); + if (state->entrance_nodes != &state->nodes) + { + re_node_set_free (state->entrance_nodes); + re_free (state->entrance_nodes); + } + re_node_set_free (&state->nodes); + re_free (state->word_trtable); + re_free (state->trtable); + re_free (state); +} + +/* Create the new state which is independ of contexts. + Return the new state if succeeded, otherwise return NULL. */ + +static re_dfastate_t * +internal_function +create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, + unsigned int hash) +{ + int i; + reg_errcode_t err; + re_dfastate_t *newstate; + + newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + if (BE (newstate == NULL, 0)) + return NULL; + err = re_node_set_init_copy (&newstate->nodes, nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_free (newstate); + return NULL; + } + + newstate->entrance_nodes = &newstate->nodes; + for (i = 0 ; i < nodes->nelem ; i++) + { + re_token_t *node = dfa->nodes + nodes->elems[i]; + re_token_type_t type = node->type; + if (type == CHARACTER && !node->constraint) + continue; +#ifdef RE_ENABLE_I18N + newstate->accept_mb |= node->accept_mb; +#endif /* RE_ENABLE_I18N */ + + /* If the state has the halt node, the state is a halt state. */ + if (type == END_OF_RE) + newstate->halt = 1; + else if (type == OP_BACK_REF) + newstate->has_backref = 1; + else if (type == ANCHOR || node->constraint) + newstate->has_constraint = 1; + } + err = register_state (dfa, newstate, hash); + if (BE (err != REG_NOERROR, 0)) + { + free_state (newstate); + newstate = NULL; + } + return newstate; +} + +/* Create the new state which is depend on the context CONTEXT. + Return the new state if succeeded, otherwise return NULL. */ + +static re_dfastate_t * +internal_function +create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, + unsigned int context, unsigned int hash) +{ + int i, nctx_nodes = 0; + reg_errcode_t err; + re_dfastate_t *newstate; + + newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + if (BE (newstate == NULL, 0)) + return NULL; + err = re_node_set_init_copy (&newstate->nodes, nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_free (newstate); + return NULL; + } + + newstate->context = context; + newstate->entrance_nodes = &newstate->nodes; + + for (i = 0 ; i < nodes->nelem ; i++) + { + re_token_t *node = dfa->nodes + nodes->elems[i]; + re_token_type_t type = node->type; + unsigned int constraint = node->constraint; + + if (type == CHARACTER && !constraint) + continue; +#ifdef RE_ENABLE_I18N + newstate->accept_mb |= node->accept_mb; +#endif /* RE_ENABLE_I18N */ + + /* If the state has the halt node, the state is a halt state. */ + if (type == END_OF_RE) + newstate->halt = 1; + else if (type == OP_BACK_REF) + newstate->has_backref = 1; + + if (constraint) + { + if (newstate->entrance_nodes == &newstate->nodes) + { + newstate->entrance_nodes = re_malloc (re_node_set, 1); + if (BE (newstate->entrance_nodes == NULL, 0)) + { + free_state (newstate); + return NULL; + } + if (re_node_set_init_copy (newstate->entrance_nodes, nodes) + != REG_NOERROR) + return NULL; + nctx_nodes = 0; + newstate->has_constraint = 1; + } + + if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context)) + { + re_node_set_remove_at (&newstate->nodes, i - nctx_nodes); + ++nctx_nodes; + } + } + } + err = register_state (dfa, newstate, hash); + if (BE (err != REG_NOERROR, 0)) + { + free_state (newstate); + newstate = NULL; + } + return newstate; +} diff --git a/deps/regex/regex_internal.h b/deps/regex/regex_internal.h new file mode 100644 index 000000000..4184d7f5a --- /dev/null +++ b/deps/regex/regex_internal.h @@ -0,0 +1,810 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002-2005, 2007, 2008, 2010 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _REGEX_INTERNAL_H +#define _REGEX_INTERNAL_H 1 + +#include +#include +#include +#include +#include + +#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC +# include +#endif +#if defined HAVE_LOCALE_H || defined _LIBC +# include +#endif +#if defined HAVE_WCHAR_H || defined _LIBC +# include +#endif /* HAVE_WCHAR_H || _LIBC */ +#if defined HAVE_WCTYPE_H || defined _LIBC +# include +#endif /* HAVE_WCTYPE_H || _LIBC */ +#if defined HAVE_STDBOOL_H || defined _LIBC +# include +#endif /* HAVE_STDBOOL_H || _LIBC */ +#if !defined(ZOS_USS) +#if defined HAVE_STDINT_H || defined _LIBC +# include +#endif /* HAVE_STDINT_H || _LIBC */ +#endif /* !ZOS_USS */ +#if defined _LIBC +# include +#else +# define __libc_lock_define(CLASS,NAME) +# define __libc_lock_init(NAME) do { } while (0) +# define __libc_lock_lock(NAME) do { } while (0) +# define __libc_lock_unlock(NAME) do { } while (0) +#endif + +#ifndef GAWK +/* In case that the system doesn't have isblank(). */ +#if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank +# define isblank(ch) ((ch) == ' ' || (ch) == '\t') +#endif +#else /* GAWK */ +/* + * This is a freaking mess. On glibc systems you have to define + * a magic constant to get isblank() out of , since it's + * a C99 function. To heck with all that and borrow a page from + * dfa.c's book. + */ + +static int +is_blank (int c) +{ + return (c == ' ' || c == '\t'); +} +#endif /* GAWK */ + +#ifdef _LIBC +# ifndef _RE_DEFINE_LOCALE_FUNCTIONS +# define _RE_DEFINE_LOCALE_FUNCTIONS 1 +# include +# include +# include +# endif +#endif + +/* This is for other GNU distributions with internationalized messages. */ +#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC +# include +# ifdef _LIBC +# undef gettext +# define gettext(msgid) \ + INTUSE(__dcgettext) (_libc_intl_domainname, msgid, LC_MESSAGES) +# endif +#else +# define gettext(msgid) (msgid) +#endif + +#ifndef gettext_noop +/* This define is so xgettext can find the internationalizable + strings. */ +# define gettext_noop(String) String +#endif + +/* For loser systems without the definition. */ +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t) -1) +#endif + +#ifndef NO_MBSUPPORT +#include "mbsupport.h" /* gawk */ +#endif +#ifndef MB_CUR_MAX +#define MB_CUR_MAX 1 +#endif + +#if (defined MBS_SUPPORT) || _LIBC +# define RE_ENABLE_I18N +#endif + +#if __GNUC__ >= 3 +# define BE(expr, val) __builtin_expect (expr, val) +#else +# define BE(expr, val) (expr) +# ifdef inline +# undef inline +# endif +# define inline +#endif + +/* Number of single byte character. */ +#define SBC_MAX 256 + +#define COLL_ELEM_LEN_MAX 8 + +/* The character which represents newline. */ +#define NEWLINE_CHAR '\n' +#define WIDE_NEWLINE_CHAR L'\n' + +/* Rename to standard API for using out of glibc. */ +#ifndef _LIBC +# ifdef __wctype +# undef __wctype +# endif +# define __wctype wctype +# ifdef __iswctype +# undef __iswctype +# endif +# define __iswctype iswctype +# define __btowc btowc +# define __mbrtowc mbrtowc +#undef __mempcpy /* GAWK */ +# define __mempcpy mempcpy +# define __wcrtomb wcrtomb +# define __regfree regfree +# define attribute_hidden +#endif /* not _LIBC */ + +#ifdef __GNUC__ +# define __attribute(arg) __attribute__ (arg) +#else +# define __attribute(arg) +#endif + +extern const char __re_error_msgid[] attribute_hidden; +extern const size_t __re_error_msgid_idx[] attribute_hidden; + +/* An integer used to represent a set of bits. It must be unsigned, + and must be at least as wide as unsigned int. */ +typedef unsigned long int bitset_word_t; +/* All bits set in a bitset_word_t. */ +#define BITSET_WORD_MAX ULONG_MAX +/* Number of bits in a bitset_word_t. */ +#define BITSET_WORD_BITS (sizeof (bitset_word_t) * CHAR_BIT) +/* Number of bitset_word_t in a bit_set. */ +#define BITSET_WORDS (SBC_MAX / BITSET_WORD_BITS) +typedef bitset_word_t bitset_t[BITSET_WORDS]; +typedef bitset_word_t *re_bitset_ptr_t; +typedef const bitset_word_t *re_const_bitset_ptr_t; + +#define bitset_set(set,i) \ + (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS) +#define bitset_clear(set,i) \ + (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS)) +#define bitset_contain(set,i) \ + (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS)) +#define bitset_empty(set) memset (set, '\0', sizeof (bitset_t)) +#define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t)) +#define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_t)) + +#define PREV_WORD_CONSTRAINT 0x0001 +#define PREV_NOTWORD_CONSTRAINT 0x0002 +#define NEXT_WORD_CONSTRAINT 0x0004 +#define NEXT_NOTWORD_CONSTRAINT 0x0008 +#define PREV_NEWLINE_CONSTRAINT 0x0010 +#define NEXT_NEWLINE_CONSTRAINT 0x0020 +#define PREV_BEGBUF_CONSTRAINT 0x0040 +#define NEXT_ENDBUF_CONSTRAINT 0x0080 +#define WORD_DELIM_CONSTRAINT 0x0100 +#define NOT_WORD_DELIM_CONSTRAINT 0x0200 + +typedef enum +{ + INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, + WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT, + WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, + INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT, + LINE_FIRST = PREV_NEWLINE_CONSTRAINT, + LINE_LAST = NEXT_NEWLINE_CONSTRAINT, + BUF_FIRST = PREV_BEGBUF_CONSTRAINT, + BUF_LAST = NEXT_ENDBUF_CONSTRAINT, + WORD_DELIM = WORD_DELIM_CONSTRAINT, + NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT +} re_context_type; + +typedef struct +{ + int alloc; + int nelem; + int *elems; +} re_node_set; + +typedef enum +{ + NON_TYPE = 0, + + /* Node type, These are used by token, node, tree. */ + CHARACTER = 1, + END_OF_RE = 2, + SIMPLE_BRACKET = 3, + OP_BACK_REF = 4, + OP_PERIOD = 5, +#ifdef RE_ENABLE_I18N + COMPLEX_BRACKET = 6, + OP_UTF8_PERIOD = 7, +#endif /* RE_ENABLE_I18N */ + + /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used + when the debugger shows values of this enum type. */ +#define EPSILON_BIT 8 + OP_OPEN_SUBEXP = EPSILON_BIT | 0, + OP_CLOSE_SUBEXP = EPSILON_BIT | 1, + OP_ALT = EPSILON_BIT | 2, + OP_DUP_ASTERISK = EPSILON_BIT | 3, + ANCHOR = EPSILON_BIT | 4, + + /* Tree type, these are used only by tree. */ + CONCAT = 16, + SUBEXP = 17, + + /* Token type, these are used only by token. */ + OP_DUP_PLUS = 18, + OP_DUP_QUESTION, + OP_OPEN_BRACKET, + OP_CLOSE_BRACKET, + OP_CHARSET_RANGE, + OP_OPEN_DUP_NUM, + OP_CLOSE_DUP_NUM, + OP_NON_MATCH_LIST, + OP_OPEN_COLL_ELEM, + OP_CLOSE_COLL_ELEM, + OP_OPEN_EQUIV_CLASS, + OP_CLOSE_EQUIV_CLASS, + OP_OPEN_CHAR_CLASS, + OP_CLOSE_CHAR_CLASS, + OP_WORD, + OP_NOTWORD, + OP_SPACE, + OP_NOTSPACE, + BACK_SLASH + +} re_token_type_t; + +#ifdef RE_ENABLE_I18N +typedef struct +{ + /* Multibyte characters. */ + wchar_t *mbchars; + + /* Collating symbols. */ +# ifdef _LIBC + int32_t *coll_syms; +# endif + + /* Equivalence classes. */ +# ifdef _LIBC + int32_t *equiv_classes; +# endif + + /* Range expressions. */ +# ifdef _LIBC + uint32_t *range_starts; + uint32_t *range_ends; +# else /* not _LIBC */ + wchar_t *range_starts; + wchar_t *range_ends; +# endif /* not _LIBC */ + + /* Character classes. */ + wctype_t *char_classes; + + /* If this character set is the non-matching list. */ + unsigned int non_match : 1; + + /* # of multibyte characters. */ + int nmbchars; + + /* # of collating symbols. */ + int ncoll_syms; + + /* # of equivalence classes. */ + int nequiv_classes; + + /* # of range expressions. */ + int nranges; + + /* # of character classes. */ + int nchar_classes; +} re_charset_t; +#endif /* RE_ENABLE_I18N */ + +typedef struct +{ + union + { + unsigned char c; /* for CHARACTER */ + re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */ +#ifdef RE_ENABLE_I18N + re_charset_t *mbcset; /* for COMPLEX_BRACKET */ +#endif /* RE_ENABLE_I18N */ + int idx; /* for BACK_REF */ + re_context_type ctx_type; /* for ANCHOR */ + } opr; +#if __GNUC__ >= 2 + re_token_type_t type : 8; +#else + re_token_type_t type; +#endif + unsigned int constraint : 10; /* context constraint */ + unsigned int duplicated : 1; + unsigned int opt_subexp : 1; +#ifdef RE_ENABLE_I18N + unsigned int accept_mb : 1; + /* These 2 bits can be moved into the union if needed (e.g. if running out + of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */ + unsigned int mb_partial : 1; +#endif + unsigned int word_char : 1; +} re_token_t; + +#define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT) + +struct re_string_t +{ + /* Indicate the raw buffer which is the original string passed as an + argument of regexec(), re_search(), etc.. */ + const unsigned char *raw_mbs; + /* Store the multibyte string. In case of "case insensitive mode" like + REG_ICASE, upper cases of the string are stored, otherwise MBS points + the same address that RAW_MBS points. */ + unsigned char *mbs; +#ifdef RE_ENABLE_I18N + /* Store the wide character string which is corresponding to MBS. */ + wint_t *wcs; + int *offsets; + mbstate_t cur_state; +#endif + /* Index in RAW_MBS. Each character mbs[i] corresponds to + raw_mbs[raw_mbs_idx + i]. */ + int raw_mbs_idx; + /* The length of the valid characters in the buffers. */ + int valid_len; + /* The corresponding number of bytes in raw_mbs array. */ + int valid_raw_len; + /* The length of the buffers MBS and WCS. */ + int bufs_len; + /* The index in MBS, which is updated by re_string_fetch_byte. */ + int cur_idx; + /* length of RAW_MBS array. */ + int raw_len; + /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */ + int len; + /* End of the buffer may be shorter than its length in the cases such + as re_match_2, re_search_2. Then, we use STOP for end of the buffer + instead of LEN. */ + int raw_stop; + /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */ + int stop; + + /* The context of mbs[0]. We store the context independently, since + the context of mbs[0] may be different from raw_mbs[0], which is + the beginning of the input string. */ + unsigned int tip_context; + /* The translation passed as a part of an argument of re_compile_pattern. */ + RE_TRANSLATE_TYPE trans; + /* Copy of re_dfa_t's word_char. */ + re_const_bitset_ptr_t word_char; + /* 1 if REG_ICASE. */ + unsigned char icase; + unsigned char is_utf8; + unsigned char map_notascii; + unsigned char mbs_allocated; + unsigned char offsets_needed; + unsigned char newline_anchor; + unsigned char word_ops_used; + int mb_cur_max; +}; +typedef struct re_string_t re_string_t; + + +struct re_dfa_t; +typedef struct re_dfa_t re_dfa_t; + +#ifndef _LIBC +# ifdef __i386__ +# define internal_function __attribute ((regparm (3), stdcall)) +# else +# define internal_function +# endif +#endif + +#ifndef NOT_IN_libc +static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr, + int new_buf_len) + internal_function; +# ifdef RE_ENABLE_I18N +static void build_wcs_buffer (re_string_t *pstr) internal_function; +static reg_errcode_t build_wcs_upper_buffer (re_string_t *pstr) + internal_function; +# endif /* RE_ENABLE_I18N */ +static void build_upper_buffer (re_string_t *pstr) internal_function; +static void re_string_translate_buffer (re_string_t *pstr) internal_function; +static unsigned int re_string_context_at (const re_string_t *input, int idx, + int eflags) + internal_function __attribute ((pure)); +#endif +#define re_string_peek_byte(pstr, offset) \ + ((pstr)->mbs[(pstr)->cur_idx + offset]) +#define re_string_fetch_byte(pstr) \ + ((pstr)->mbs[(pstr)->cur_idx++]) +#define re_string_first_byte(pstr, idx) \ + ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF) +#define re_string_is_single_byte_char(pstr, idx) \ + ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \ + || (pstr)->wcs[(idx) + 1] != WEOF)) +#define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx) +#define re_string_cur_idx(pstr) ((pstr)->cur_idx) +#define re_string_get_buffer(pstr) ((pstr)->mbs) +#define re_string_length(pstr) ((pstr)->len) +#define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx]) +#define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx)) +#define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx)) + +#ifndef _LIBC +# if HAVE_ALLOCA +# if (_MSC_VER) +# include +# define __libc_use_alloca(n) 0 +# else +# include +/* The OS usually guarantees only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + allocate anything larger than 4096 bytes. Also care for the possibility + of a few compiler-allocated temporary stack slots. */ +# define __libc_use_alloca(n) ((n) < 4032) +# endif +# else +/* alloca is implemented with malloc, so just use malloc. */ +# define __libc_use_alloca(n) 0 +# endif +#endif + +#define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t))) +/* SunOS 4.1.x realloc doesn't accept null pointers: pre-Standard C. Sigh. */ +#define re_realloc(p,t,n) ((p != NULL) ? (t *) realloc (p,(n)*sizeof(t)) : (t *) calloc(n,sizeof(t))) +#define re_free(p) free (p) + +struct bin_tree_t +{ + struct bin_tree_t *parent; + struct bin_tree_t *left; + struct bin_tree_t *right; + struct bin_tree_t *first; + struct bin_tree_t *next; + + re_token_t token; + + /* `node_idx' is the index in dfa->nodes, if `type' == 0. + Otherwise `type' indicate the type of this node. */ + int node_idx; +}; +typedef struct bin_tree_t bin_tree_t; + +#define BIN_TREE_STORAGE_SIZE \ + ((1024 - sizeof (void *)) / sizeof (bin_tree_t)) + +struct bin_tree_storage_t +{ + struct bin_tree_storage_t *next; + bin_tree_t data[BIN_TREE_STORAGE_SIZE]; +}; +typedef struct bin_tree_storage_t bin_tree_storage_t; + +#define CONTEXT_WORD 1 +#define CONTEXT_NEWLINE (CONTEXT_WORD << 1) +#define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1) +#define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1) + +#define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD) +#define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE) +#define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF) +#define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF) +#define IS_ORDINARY_CONTEXT(c) ((c) == 0) + +#define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_') +#define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR) +#define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_') +#define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR) + +#define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \ + ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ + || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ + || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\ + || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context))) + +#define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \ + ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \ + || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \ + || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \ + || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context))) + +struct re_dfastate_t +{ + unsigned int hash; + re_node_set nodes; + re_node_set non_eps_nodes; + re_node_set inveclosure; + re_node_set *entrance_nodes; + struct re_dfastate_t **trtable, **word_trtable; + unsigned int context : 4; + unsigned int halt : 1; + /* If this state can accept `multi byte'. + Note that we refer to multibyte characters, and multi character + collating elements as `multi byte'. */ + unsigned int accept_mb : 1; + /* If this state has backreference node(s). */ + unsigned int has_backref : 1; + unsigned int has_constraint : 1; +}; +typedef struct re_dfastate_t re_dfastate_t; + +struct re_state_table_entry +{ + int num; + int alloc; + re_dfastate_t **array; +}; + +/* Array type used in re_sub_match_last_t and re_sub_match_top_t. */ + +typedef struct +{ + int next_idx; + int alloc; + re_dfastate_t **array; +} state_array_t; + +/* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */ + +typedef struct +{ + int node; + int str_idx; /* The position NODE match at. */ + state_array_t path; +} re_sub_match_last_t; + +/* Store information about the node NODE whose type is OP_OPEN_SUBEXP. + And information about the node, whose type is OP_CLOSE_SUBEXP, + corresponding to NODE is stored in LASTS. */ + +typedef struct +{ + int str_idx; + int node; + state_array_t *path; + int alasts; /* Allocation size of LASTS. */ + int nlasts; /* The number of LASTS. */ + re_sub_match_last_t **lasts; +} re_sub_match_top_t; + +struct re_backref_cache_entry +{ + int node; + int str_idx; + int subexp_from; + int subexp_to; + char more; + char unused; + unsigned short int eps_reachable_subexps_map; +}; + +typedef struct +{ + /* The string object corresponding to the input string. */ + re_string_t input; +#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) + const re_dfa_t *const dfa; +#else + const re_dfa_t *dfa; +#endif + /* EFLAGS of the argument of regexec. */ + int eflags; + /* Where the matching ends. */ + int match_last; + int last_node; + /* The state log used by the matcher. */ + re_dfastate_t **state_log; + int state_log_top; + /* Back reference cache. */ + int nbkref_ents; + int abkref_ents; + struct re_backref_cache_entry *bkref_ents; + int max_mb_elem_len; + int nsub_tops; + int asub_tops; + re_sub_match_top_t **sub_tops; +} re_match_context_t; + +typedef struct +{ + re_dfastate_t **sifted_states; + re_dfastate_t **limited_states; + int last_node; + int last_str_idx; + re_node_set limits; +} re_sift_context_t; + +struct re_fail_stack_ent_t +{ + int idx; + int node; + regmatch_t *regs; + re_node_set eps_via_nodes; +}; + +struct re_fail_stack_t +{ + int num; + int alloc; + struct re_fail_stack_ent_t *stack; +}; + +struct re_dfa_t +{ + re_token_t *nodes; + size_t nodes_alloc; + size_t nodes_len; + int *nexts; + int *org_indices; + re_node_set *edests; + re_node_set *eclosures; + re_node_set *inveclosures; + struct re_state_table_entry *state_table; + re_dfastate_t *init_state; + re_dfastate_t *init_state_word; + re_dfastate_t *init_state_nl; + re_dfastate_t *init_state_begbuf; + bin_tree_t *str_tree; + bin_tree_storage_t *str_tree_storage; + re_bitset_ptr_t sb_char; + int str_tree_storage_idx; + + /* number of subexpressions `re_nsub' is in regex_t. */ + unsigned int state_hash_mask; + int init_node; + int nbackref; /* The number of backreference in this dfa. */ + + /* Bitmap expressing which backreference is used. */ + bitset_word_t used_bkref_map; + bitset_word_t completed_bkref_map; + + unsigned int has_plural_match : 1; + /* If this dfa has "multibyte node", which is a backreference or + a node which can accept multibyte character or multi character + collating element. */ + unsigned int has_mb_node : 1; + unsigned int is_utf8 : 1; + unsigned int map_notascii : 1; + unsigned int word_ops_used : 1; + int mb_cur_max; + bitset_t word_char; + reg_syntax_t syntax; + int *subexp_map; +#ifdef DEBUG + char* re_str; +#endif +#if defined _LIBC + __libc_lock_define (, lock) +#endif +}; + +#define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set)) +#define re_node_set_remove(set,id) \ + (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1)) +#define re_node_set_empty(p) ((p)->nelem = 0) +#define re_node_set_free(set) re_free ((set)->elems) + + +typedef enum +{ + SB_CHAR, + MB_CHAR, + EQUIV_CLASS, + COLL_SYM, + CHAR_CLASS +} bracket_elem_type; + +typedef struct +{ + bracket_elem_type type; + union + { + unsigned char ch; + unsigned char *name; + wchar_t wch; + } opr; +} bracket_elem_t; + + +/* Inline functions for bitset operation. */ +static inline void +bitset_not (bitset_t set) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) + set[bitset_i] = ~set[bitset_i]; +} + +static inline void +bitset_merge (bitset_t dest, const bitset_t src) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) + dest[bitset_i] |= src[bitset_i]; +} + +static inline void +bitset_mask (bitset_t dest, const bitset_t src) +{ + int bitset_i; + for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i) + dest[bitset_i] &= src[bitset_i]; +} + +#ifdef RE_ENABLE_I18N +/* Inline functions for re_string. */ +static inline int +internal_function __attribute ((pure)) +re_string_char_size_at (const re_string_t *pstr, int idx) +{ + int byte_idx; + if (pstr->mb_cur_max == 1) + return 1; + for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx) + if (pstr->wcs[idx + byte_idx] != WEOF) + break; + return byte_idx; +} + +static inline wint_t +internal_function __attribute ((pure)) +re_string_wchar_at (const re_string_t *pstr, int idx) +{ + if (pstr->mb_cur_max == 1) + return (wint_t) pstr->mbs[idx]; + return (wint_t) pstr->wcs[idx]; +} + +# ifndef NOT_IN_libc +static int +internal_function __attribute ((pure)) +re_string_elem_size_at (const re_string_t *pstr, int idx) +{ +# ifdef _LIBC + const unsigned char *p, *extra; + const int32_t *table, *indirect; + int32_t tmp; +# include + uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + + if (nrules != 0) + { + table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, + _NL_COLLATE_INDIRECTMB); + p = pstr->mbs + idx; + tmp = findidx (&p); + return p - pstr->mbs - idx; + } + else +# endif /* _LIBC */ + return 1; +} +# endif +#endif /* RE_ENABLE_I18N */ + +#endif /* _REGEX_INTERNAL_H */ diff --git a/deps/regex/regexec.c b/deps/regex/regexec.c new file mode 100644 index 000000000..5eb6f1fea --- /dev/null +++ b/deps/regex/regexec.c @@ -0,0 +1,4369 @@ +/* Extended regular expression matching and search library. + Copyright (C) 2002-2005, 2007, 2009, 2010 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Isamu Hasegawa . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags, + int n) internal_function; +static void match_ctx_clean (re_match_context_t *mctx) internal_function; +static void match_ctx_free (re_match_context_t *cache) internal_function; +static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node, + int str_idx, int from, int to) + internal_function; +static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) + internal_function; +static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node, + int str_idx) internal_function; +static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop, + int node, int str_idx) + internal_function; +static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, + re_dfastate_t **limited_sts, int last_node, + int last_str_idx) + internal_function; +static reg_errcode_t re_search_internal (const regex_t *preg, + const char *string, int length, + int start, int range, int stop, + size_t nmatch, regmatch_t pmatch[], + int eflags); +static int re_search_2_stub (struct re_pattern_buffer *bufp, + const char *string1, int length1, + const char *string2, int length2, + int start, int range, struct re_registers *regs, + int stop, int ret_len); +static int re_search_stub (struct re_pattern_buffer *bufp, + const char *string, int length, int start, + int range, int stop, struct re_registers *regs, + int ret_len); +static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch, + unsigned int nregs, int regs_allocated); +static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx); +static int check_matching (re_match_context_t *mctx, int fl_longest_match, + int *p_match_first) internal_function; +static int check_halt_state_context (const re_match_context_t *mctx, + const re_dfastate_t *state, int idx) + internal_function; +static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, + regmatch_t *prev_idx_match, int cur_node, + int cur_idx, int nmatch) internal_function; +static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs, + int str_idx, int dest_node, int nregs, + regmatch_t *regs, + re_node_set *eps_via_nodes) + internal_function; +static reg_errcode_t set_regs (const regex_t *preg, + const re_match_context_t *mctx, + size_t nmatch, regmatch_t *pmatch, + int fl_backtrack) internal_function; +static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs) + internal_function; + +#ifdef RE_ENABLE_I18N +static int sift_states_iter_mb (const re_match_context_t *mctx, + re_sift_context_t *sctx, + int node_idx, int str_idx, int max_str_idx) + internal_function; +#endif /* RE_ENABLE_I18N */ +static reg_errcode_t sift_states_backward (const re_match_context_t *mctx, + re_sift_context_t *sctx) + internal_function; +static reg_errcode_t build_sifted_states (const re_match_context_t *mctx, + re_sift_context_t *sctx, int str_idx, + re_node_set *cur_dest) + internal_function; +static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx, + re_sift_context_t *sctx, + int str_idx, + re_node_set *dest_nodes) + internal_function; +static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa, + re_node_set *dest_nodes, + const re_node_set *candidates) + internal_function; +static int check_dst_limits (const re_match_context_t *mctx, + re_node_set *limits, + int dst_node, int dst_idx, int src_node, + int src_idx) internal_function; +static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, + int boundaries, int subexp_idx, + int from_node, int bkref_idx) + internal_function; +static int check_dst_limits_calc_pos (const re_match_context_t *mctx, + int limit, int subexp_idx, + int node, int str_idx, + int bkref_idx) internal_function; +static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa, + re_node_set *dest_nodes, + const re_node_set *candidates, + re_node_set *limits, + struct re_backref_cache_entry *bkref_ents, + int str_idx) internal_function; +static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx, + re_sift_context_t *sctx, + int str_idx, const re_node_set *candidates) + internal_function; +static reg_errcode_t merge_state_array (const re_dfa_t *dfa, + re_dfastate_t **dst, + re_dfastate_t **src, int num) + internal_function; +static re_dfastate_t *find_recover_state (reg_errcode_t *err, + re_match_context_t *mctx) internal_function; +static re_dfastate_t *transit_state (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *state) internal_function; +static re_dfastate_t *merge_state_with_log (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *next_state) + internal_function; +static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx, + re_node_set *cur_nodes, + int str_idx) internal_function; +#if 0 +static re_dfastate_t *transit_state_sb (reg_errcode_t *err, + re_match_context_t *mctx, + re_dfastate_t *pstate) + internal_function; +#endif +#ifdef RE_ENABLE_I18N +static reg_errcode_t transit_state_mb (re_match_context_t *mctx, + re_dfastate_t *pstate) + internal_function; +#endif /* RE_ENABLE_I18N */ +static reg_errcode_t transit_state_bkref (re_match_context_t *mctx, + const re_node_set *nodes) + internal_function; +static reg_errcode_t get_subexp (re_match_context_t *mctx, + int bkref_node, int bkref_str_idx) + internal_function; +static reg_errcode_t get_subexp_sub (re_match_context_t *mctx, + const re_sub_match_top_t *sub_top, + re_sub_match_last_t *sub_last, + int bkref_node, int bkref_str) + internal_function; +static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, + int subexp_idx, int type) internal_function; +static reg_errcode_t check_arrival (re_match_context_t *mctx, + state_array_t *path, int top_node, + int top_str, int last_node, int last_str, + int type) internal_function; +static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx, + int str_idx, + re_node_set *cur_nodes, + re_node_set *next_nodes) + internal_function; +static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa, + re_node_set *cur_nodes, + int ex_subexp, int type) + internal_function; +static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa, + re_node_set *dst_nodes, + int target, int ex_subexp, + int type) internal_function; +static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx, + re_node_set *cur_nodes, int cur_str, + int subexp_num, int type) + internal_function; +static int build_trtable (const re_dfa_t *dfa, + re_dfastate_t *state) internal_function; +#ifdef RE_ENABLE_I18N +static int check_node_accept_bytes (const re_dfa_t *dfa, int node_idx, + const re_string_t *input, int idx) + internal_function; +# ifdef _LIBC +static unsigned int find_collation_sequence_value (const unsigned char *mbs, + size_t name_len) + internal_function; +# endif /* _LIBC */ +#endif /* RE_ENABLE_I18N */ +static int group_nodes_into_DFAstates (const re_dfa_t *dfa, + const re_dfastate_t *state, + re_node_set *states_node, + bitset_t *states_ch) internal_function; +static int check_node_accept (const re_match_context_t *mctx, + const re_token_t *node, int idx) + internal_function; +static reg_errcode_t extend_buffers (re_match_context_t *mctx) + internal_function; + +/* Entry point for POSIX code. */ + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +int +regexec ( + const regex_t *__restrict preg, + const char *__restrict string, + size_t nmatch, + regmatch_t pmatch[], + int eflags) +{ + reg_errcode_t err; + int start, length; + + if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND)) + return REG_BADPAT; + + if (eflags & REG_STARTEND) + { + start = pmatch[0].rm_so; + length = pmatch[0].rm_eo; + } + else + { + start = 0; + length = strlen (string); + } + + __libc_lock_lock (dfa->lock); + if (preg->no_sub) + err = re_search_internal (preg, string, length, start, length - start, + length, 0, NULL, eflags); + else + err = re_search_internal (preg, string, length, start, length - start, + length, nmatch, pmatch, eflags); + __libc_lock_unlock (dfa->lock); + return err != REG_NOERROR; +} + +#ifdef _LIBC +# include +versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4); + +# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4) +__typeof__ (__regexec) __compat_regexec; + +int +attribute_compat_text_section +__compat_regexec (const regex_t *__restrict preg, + const char *__restrict string, size_t nmatch, + regmatch_t pmatch[], int eflags) +{ + return regexec (preg, string, nmatch, pmatch, + eflags & (REG_NOTBOL | REG_NOTEOL)); +} +compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0); +# endif +#endif + +/* Entry points for GNU code. */ + +/* re_match, re_search, re_match_2, re_search_2 + + The former two functions operate on STRING with length LENGTH, + while the later two operate on concatenation of STRING1 and STRING2 + with lengths LENGTH1 and LENGTH2, respectively. + + re_match() matches the compiled pattern in BUFP against the string, + starting at index START. + + re_search() first tries matching at index START, then it tries to match + starting from index START + 1, and so on. The last start position tried + is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same + way as re_match().) + + The parameter STOP of re_{match,search}_2 specifies that no match exceeding + the first STOP characters of the concatenation of the strings should be + concerned. + + If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match + and all groups is stroed in REGS. (For the "_2" variants, the offsets are + computed relative to the concatenation, not relative to the individual + strings.) + + On success, re_match* functions return the length of the match, re_search* + return the position of the start of the match. Return value -1 means no + match was found and -2 indicates an internal error. */ + +int +re_match (struct re_pattern_buffer *bufp, + const char *string, + int length, + int start, + struct re_registers *regs) +{ + return re_search_stub (bufp, string, length, start, 0, length, regs, 1); +} +#ifdef _LIBC +weak_alias (__re_match, re_match) +#endif + +int +re_search (struct re_pattern_buffer *bufp, + const char *string, + int length, int start, int range, + struct re_registers *regs) +{ + return re_search_stub (bufp, string, length, start, range, length, regs, 0); +} +#ifdef _LIBC +weak_alias (__re_search, re_search) +#endif + +int +re_match_2 (struct re_pattern_buffer *bufp, + const char *string1, int length1, + const char *string2, int length2, int start, + struct re_registers *regs, int stop) +{ + return re_search_2_stub (bufp, string1, length1, string2, length2, + start, 0, regs, stop, 1); +} +#ifdef _LIBC +weak_alias (__re_match_2, re_match_2) +#endif + +int +re_search_2 (struct re_pattern_buffer *bufp, + const char *string1, int length1, + const char *string2, int length2, int start, + int range, struct re_registers *regs, int stop) +{ + return re_search_2_stub (bufp, string1, length1, string2, length2, + start, range, regs, stop, 0); +} +#ifdef _LIBC +weak_alias (__re_search_2, re_search_2) +#endif + +static int +re_search_2_stub (struct re_pattern_buffer *bufp, + const char *string1, int length1, + const char *string2, int length2, int start, + int range, struct re_registers *regs, + int stop, int ret_len) +{ + const char *str; + int rval; + int len = length1 + length2; + int free_str = 0; + + if (BE (length1 < 0 || length2 < 0 || stop < 0, 0)) + return -2; + + /* Concatenate the strings. */ + if (length2 > 0) + if (length1 > 0) + { + char *s = re_malloc (char, len); + + if (BE (s == NULL, 0)) + return -2; + memcpy (s, string1, length1); + memcpy (s + length1, string2, length2); + str = s; + free_str = 1; + } + else + str = string2; + else + str = string1; + + rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len); + if (free_str) + re_free ((char *) str); + return rval; +} + +/* The parameters have the same meaning as those of re_search. + Additional parameters: + If RET_LEN is nonzero the length of the match is returned (re_match style); + otherwise the position of the match is returned. */ + +static int +re_search_stub (struct re_pattern_buffer *bufp, + const char *string, int length, int start, + int range, int stop, + struct re_registers *regs, int ret_len) +{ + reg_errcode_t result; + regmatch_t *pmatch; + int nregs, rval; + int eflags = 0; + + /* Check for out-of-range. */ + if (BE (start < 0 || start > length, 0)) + return -1; + if (BE (start + range > length, 0)) + range = length - start; + else if (BE (start + range < 0, 0)) + range = -start; + + __libc_lock_lock (dfa->lock); + + eflags |= (bufp->not_bol) ? REG_NOTBOL : 0; + eflags |= (bufp->not_eol) ? REG_NOTEOL : 0; + + /* Compile fastmap if we haven't yet. */ + if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate) + re_compile_fastmap (bufp); + + if (BE (bufp->no_sub, 0)) + regs = NULL; + + /* We need at least 1 register. */ + if (regs == NULL) + nregs = 1; + else if (BE (bufp->regs_allocated == REGS_FIXED && + regs->num_regs < bufp->re_nsub + 1, 0)) + { + nregs = regs->num_regs; + if (BE (nregs < 1, 0)) + { + /* Nothing can be copied to regs. */ + regs = NULL; + nregs = 1; + } + } + else + nregs = bufp->re_nsub + 1; + pmatch = re_malloc (regmatch_t, nregs); + if (BE (pmatch == NULL, 0)) + { + rval = -2; + goto out; + } + + result = re_search_internal (bufp, string, length, start, range, stop, + nregs, pmatch, eflags); + + rval = 0; + + /* I hope we needn't fill ther regs with -1's when no match was found. */ + if (result != REG_NOERROR) + rval = -1; + else if (regs != NULL) + { + /* If caller wants register contents data back, copy them. */ + bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs, + bufp->regs_allocated); + if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0)) + rval = -2; + } + + if (BE (rval == 0, 1)) + { + if (ret_len) + { + assert (pmatch[0].rm_so == start); + rval = pmatch[0].rm_eo - start; + } + else + rval = pmatch[0].rm_so; + } + re_free (pmatch); + out: + __libc_lock_unlock (dfa->lock); + return rval; +} + +static unsigned +re_copy_regs (struct re_registers *regs, + regmatch_t *pmatch, + unsigned int nregs, int regs_allocated) +{ + int rval = REGS_REALLOCATE; + unsigned int i; + unsigned int need_regs = nregs + 1; + /* We need one extra element beyond `num_regs' for the `-1' marker GNU code + uses. */ + + /* Have the register data arrays been allocated? */ + if (regs_allocated == REGS_UNALLOCATED) + { /* No. So allocate them with malloc. */ + regs->start = re_malloc (regoff_t, need_regs); + if (BE (regs->start == NULL, 0)) + return REGS_UNALLOCATED; + regs->end = re_malloc (regoff_t, need_regs); + if (BE (regs->end == NULL, 0)) + { + re_free (regs->start); + return REGS_UNALLOCATED; + } + regs->num_regs = need_regs; + } + else if (regs_allocated == REGS_REALLOCATE) + { /* Yes. If we need more elements than were already + allocated, reallocate them. If we need fewer, just + leave it alone. */ + if (BE (need_regs > regs->num_regs, 0)) + { + regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs); + regoff_t *new_end; + if (BE (new_start == NULL, 0)) + return REGS_UNALLOCATED; + new_end = re_realloc (regs->end, regoff_t, need_regs); + if (BE (new_end == NULL, 0)) + { + re_free (new_start); + return REGS_UNALLOCATED; + } + regs->start = new_start; + regs->end = new_end; + regs->num_regs = need_regs; + } + } + else + { + assert (regs_allocated == REGS_FIXED); + /* This function may not be called with REGS_FIXED and nregs too big. */ + assert (regs->num_regs >= nregs); + rval = REGS_FIXED; + } + + /* Copy the regs. */ + for (i = 0; i < nregs; ++i) + { + regs->start[i] = pmatch[i].rm_so; + regs->end[i] = pmatch[i].rm_eo; + } + for ( ; i < regs->num_regs; ++i) + regs->start[i] = regs->end[i] = -1; + + return rval; +} + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use + this memory for recording register information. STARTS and ENDS + must be allocated using the malloc library routine, and must each + be at least NUM_REGS * sizeof (regoff_t) bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ + +void +re_set_registers (struct re_pattern_buffer *bufp, + struct re_registers *regs, + unsigned num_regs, + regoff_t *starts, + regoff_t *ends) +{ + if (num_regs) + { + bufp->regs_allocated = REGS_REALLOCATE; + regs->num_regs = num_regs; + regs->start = starts; + regs->end = ends; + } + else + { + bufp->regs_allocated = REGS_UNALLOCATED; + regs->num_regs = 0; + regs->start = regs->end = (regoff_t *) 0; + } +} +#ifdef _LIBC +weak_alias (__re_set_registers, re_set_registers) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC +int +# ifdef _LIBC +weak_function +# endif +re_exec (s) + const char *s; +{ + return 0 == regexec (&re_comp_buf, s, 0, NULL, 0); +} +#endif /* _REGEX_RE_COMP */ + +/* Internal entry point. */ + +/* Searches for a compiled pattern PREG in the string STRING, whose + length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same + mingings with regexec. START, and RANGE have the same meanings + with re_search. + Return REG_NOERROR if we find a match, and REG_NOMATCH if not, + otherwise return the error code. + Note: We assume front end functions already check ranges. + (START + RANGE >= 0 && START + RANGE <= LENGTH) */ + +static reg_errcode_t +re_search_internal (const regex_t *preg, + const char *string, + int length, int start, int range, int stop, + size_t nmatch, regmatch_t pmatch[], + int eflags) +{ + reg_errcode_t err; + const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; + int left_lim, right_lim, incr; + int fl_longest_match, match_first, match_kind, match_last = -1; + unsigned int extra_nmatch; + int sb, ch; +#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) + re_match_context_t mctx = { .dfa = dfa }; +#else + re_match_context_t mctx; +#endif + char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate + && range && !preg->can_be_null) ? preg->fastmap : NULL; + RE_TRANSLATE_TYPE t = preg->translate; + +#if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)) + memset (&mctx, '\0', sizeof (re_match_context_t)); + mctx.dfa = dfa; +#endif + + extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0; + nmatch -= extra_nmatch; + + /* Check if the DFA haven't been compiled. */ + if (BE (preg->used == 0 || dfa->init_state == NULL + || dfa->init_state_word == NULL || dfa->init_state_nl == NULL + || dfa->init_state_begbuf == NULL, 0)) + return REG_NOMATCH; + +#ifdef DEBUG + /* We assume front-end functions already check them. */ + assert (start + range >= 0 && start + range <= length); +#endif + + /* If initial states with non-begbuf contexts have no elements, + the regex must be anchored. If preg->newline_anchor is set, + we'll never use init_state_nl, so do not check it. */ + if (dfa->init_state->nodes.nelem == 0 + && dfa->init_state_word->nodes.nelem == 0 + && (dfa->init_state_nl->nodes.nelem == 0 + || !preg->newline_anchor)) + { + if (start != 0 && start + range != 0) + return REG_NOMATCH; + start = range = 0; + } + + /* We must check the longest matching, if nmatch > 0. */ + fl_longest_match = (nmatch != 0 || dfa->nbackref); + + err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1, + preg->translate, preg->syntax & RE_ICASE, dfa); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + mctx.input.stop = stop; + mctx.input.raw_stop = stop; + mctx.input.newline_anchor = preg->newline_anchor; + + err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* We will log all the DFA states through which the dfa pass, + if nmatch > 1, or this dfa has "multibyte node", which is a + back-reference or a node which can accept multibyte character or + multi character collating element. */ + if (nmatch > 1 || dfa->has_mb_node) + { + /* Avoid overflow. */ + if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= mctx.input.bufs_len, 0)) + { + err = REG_ESPACE; + goto free_return; + } + + mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1); + if (BE (mctx.state_log == NULL, 0)) + { + err = REG_ESPACE; + goto free_return; + } + } + else + mctx.state_log = NULL; + + match_first = start; + mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF + : CONTEXT_NEWLINE | CONTEXT_BEGBUF; + + /* Check incrementally whether of not the input string match. */ + incr = (range < 0) ? -1 : 1; + left_lim = (range < 0) ? start + range : start; + right_lim = (range < 0) ? start : start + range; + sb = dfa->mb_cur_max == 1; + match_kind = + (fastmap + ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0) + | (range >= 0 ? 2 : 0) + | (t != NULL ? 1 : 0)) + : 8); + + for (;; match_first += incr) + { + err = REG_NOMATCH; + if (match_first < left_lim || right_lim < match_first) + goto free_return; + + /* Advance as rapidly as possible through the string, until we + find a plausible place to start matching. This may be done + with varying efficiency, so there are various possibilities: + only the most common of them are specialized, in order to + save on code size. We use a switch statement for speed. */ + switch (match_kind) + { + case 8: + /* No fastmap. */ + break; + + case 7: + /* Fastmap with single-byte translation, match forward. */ + while (BE (match_first < right_lim, 1) + && !fastmap[t[(unsigned char) string[match_first]]]) + ++match_first; + goto forward_match_found_start_or_reached_end; + + case 6: + /* Fastmap without translation, match forward. */ + while (BE (match_first < right_lim, 1) + && !fastmap[(unsigned char) string[match_first]]) + ++match_first; + + forward_match_found_start_or_reached_end: + if (BE (match_first == right_lim, 0)) + { + ch = match_first >= length + ? 0 : (unsigned char) string[match_first]; + if (!fastmap[t ? t[ch] : ch]) + goto free_return; + } + break; + + case 4: + case 5: + /* Fastmap without multi-byte translation, match backwards. */ + while (match_first >= left_lim) + { + ch = match_first >= length + ? 0 : (unsigned char) string[match_first]; + if (fastmap[t ? t[ch] : ch]) + break; + --match_first; + } + if (match_first < left_lim) + goto free_return; + break; + + default: + /* In this case, we can't determine easily the current byte, + since it might be a component byte of a multibyte + character. Then we use the constructed buffer instead. */ + for (;;) + { + /* If MATCH_FIRST is out of the valid range, reconstruct the + buffers. */ + unsigned int offset = match_first - mctx.input.raw_mbs_idx; + if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0)) + { + err = re_string_reconstruct (&mctx.input, match_first, + eflags); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + offset = match_first - mctx.input.raw_mbs_idx; + } + /* If MATCH_FIRST is out of the buffer, leave it as '\0'. + Note that MATCH_FIRST must not be smaller than 0. */ + ch = (match_first >= length + ? 0 : re_string_byte_at (&mctx.input, offset)); + if (fastmap[ch]) + break; + match_first += incr; + if (match_first < left_lim || match_first > right_lim) + { + err = REG_NOMATCH; + goto free_return; + } + } + break; + } + + /* Reconstruct the buffers so that the matcher can assume that + the matching starts from the beginning of the buffer. */ + err = re_string_reconstruct (&mctx.input, match_first, eflags); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + +#ifdef RE_ENABLE_I18N + /* Don't consider this char as a possible match start if it part, + yet isn't the head, of a multibyte character. */ + if (!sb && !re_string_first_byte (&mctx.input, 0)) + continue; +#endif + + /* It seems to be appropriate one, then use the matcher. */ + /* We assume that the matching starts from 0. */ + mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0; + match_last = check_matching (&mctx, fl_longest_match, + range >= 0 ? &match_first : NULL); + if (match_last != -1) + { + if (BE (match_last == -2, 0)) + { + err = REG_ESPACE; + goto free_return; + } + else + { + mctx.match_last = match_last; + if ((!preg->no_sub && nmatch > 1) || dfa->nbackref) + { + re_dfastate_t *pstate = mctx.state_log[match_last]; + mctx.last_node = check_halt_state_context (&mctx, pstate, + match_last); + } + if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match) + || dfa->nbackref) + { + err = prune_impossible_nodes (&mctx); + if (err == REG_NOERROR) + break; + if (BE (err != REG_NOMATCH, 0)) + goto free_return; + match_last = -1; + } + else + break; /* We found a match. */ + } + } + + match_ctx_clean (&mctx); + } + +#ifdef DEBUG + assert (match_last != -1); + assert (err == REG_NOERROR); +#endif + + /* Set pmatch[] if we need. */ + if (nmatch > 0) + { + unsigned int reg_idx; + + /* Initialize registers. */ + for (reg_idx = 1; reg_idx < nmatch; ++reg_idx) + pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1; + + /* Set the points where matching start/end. */ + pmatch[0].rm_so = 0; + pmatch[0].rm_eo = mctx.match_last; + + if (!preg->no_sub && nmatch > 1) + { + err = set_regs (preg, &mctx, nmatch, pmatch, + dfa->has_plural_match && dfa->nbackref > 0); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + + /* At last, add the offset to the each registers, since we slided + the buffers so that we could assume that the matching starts + from 0. */ + for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) + if (pmatch[reg_idx].rm_so != -1) + { +#ifdef RE_ENABLE_I18N + if (BE (mctx.input.offsets_needed != 0, 0)) + { + pmatch[reg_idx].rm_so = + (pmatch[reg_idx].rm_so == mctx.input.valid_len + ? mctx.input.valid_raw_len + : mctx.input.offsets[pmatch[reg_idx].rm_so]); + pmatch[reg_idx].rm_eo = + (pmatch[reg_idx].rm_eo == mctx.input.valid_len + ? mctx.input.valid_raw_len + : mctx.input.offsets[pmatch[reg_idx].rm_eo]); + } +#else + assert (mctx.input.offsets_needed == 0); +#endif + pmatch[reg_idx].rm_so += match_first; + pmatch[reg_idx].rm_eo += match_first; + } + for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx) + { + pmatch[nmatch + reg_idx].rm_so = -1; + pmatch[nmatch + reg_idx].rm_eo = -1; + } + + if (dfa->subexp_map) + for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++) + if (dfa->subexp_map[reg_idx] != reg_idx) + { + pmatch[reg_idx + 1].rm_so + = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so; + pmatch[reg_idx + 1].rm_eo + = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo; + } + } + + free_return: + re_free (mctx.state_log); + if (dfa->nbackref) + match_ctx_free (&mctx); + re_string_destruct (&mctx.input); + return err; +} + +static reg_errcode_t +prune_impossible_nodes (re_match_context_t *mctx) +{ + const re_dfa_t *const dfa = mctx->dfa; + int halt_node, match_last; + reg_errcode_t ret; + re_dfastate_t **sifted_states; + re_dfastate_t **lim_states = NULL; + re_sift_context_t sctx; +#ifdef DEBUG + assert (mctx->state_log != NULL); +#endif + match_last = mctx->match_last; + halt_node = mctx->last_node; + + /* Avoid overflow. */ + if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= match_last, 0)) + return REG_ESPACE; + + sifted_states = re_malloc (re_dfastate_t *, match_last + 1); + if (BE (sifted_states == NULL, 0)) + { + ret = REG_ESPACE; + goto free_return; + } + if (dfa->nbackref) + { + lim_states = re_malloc (re_dfastate_t *, match_last + 1); + if (BE (lim_states == NULL, 0)) + { + ret = REG_ESPACE; + goto free_return; + } + while (1) + { + memset (lim_states, '\0', + sizeof (re_dfastate_t *) * (match_last + 1)); + sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, + match_last); + ret = sift_states_backward (mctx, &sctx); + re_node_set_free (&sctx.limits); + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + if (sifted_states[0] != NULL || lim_states[0] != NULL) + break; + do + { + --match_last; + if (match_last < 0) + { + ret = REG_NOMATCH; + goto free_return; + } + } while (mctx->state_log[match_last] == NULL + || !mctx->state_log[match_last]->halt); + halt_node = check_halt_state_context (mctx, + mctx->state_log[match_last], + match_last); + } + ret = merge_state_array (dfa, sifted_states, lim_states, + match_last + 1); + re_free (lim_states); + lim_states = NULL; + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + } + else + { + sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last); + ret = sift_states_backward (mctx, &sctx); + re_node_set_free (&sctx.limits); + if (BE (ret != REG_NOERROR, 0)) + goto free_return; + if (sifted_states[0] == NULL) + { + ret = REG_NOMATCH; + goto free_return; + } + } + re_free (mctx->state_log); + mctx->state_log = sifted_states; + sifted_states = NULL; + mctx->last_node = halt_node; + mctx->match_last = match_last; + ret = REG_NOERROR; + free_return: + re_free (sifted_states); + re_free (lim_states); + return ret; +} + +/* Acquire an initial state and return it. + We must select appropriate initial state depending on the context, + since initial states may have constraints like "\<", "^", etc.. */ + +static inline re_dfastate_t * +__attribute ((always_inline)) internal_function +acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx, + int idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + if (dfa->init_state->has_constraint) + { + unsigned int context; + context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags); + if (IS_WORD_CONTEXT (context)) + return dfa->init_state_word; + else if (IS_ORDINARY_CONTEXT (context)) + return dfa->init_state; + else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context)) + return dfa->init_state_begbuf; + else if (IS_NEWLINE_CONTEXT (context)) + return dfa->init_state_nl; + else if (IS_BEGBUF_CONTEXT (context)) + { + /* It is relatively rare case, then calculate on demand. */ + return re_acquire_state_context (err, dfa, + dfa->init_state->entrance_nodes, + context); + } + else + /* Must not happen? */ + return dfa->init_state; + } + else + return dfa->init_state; +} + +/* Check whether the regular expression match input string INPUT or not, + and return the index where the matching end, return -1 if not match, + or return -2 in case of an error. + FL_LONGEST_MATCH means we want the POSIX longest matching. + If P_MATCH_FIRST is not NULL, and the match fails, it is set to the + next place where we may want to try matching. + Note that the matcher assume that the maching starts from the current + index of the buffer. */ + +static int +internal_function +check_matching (re_match_context_t *mctx, int fl_longest_match, + int *p_match_first) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int match = 0; + int match_last = -1; + int cur_str_idx = re_string_cur_idx (&mctx->input); + re_dfastate_t *cur_state; + int at_init_state = p_match_first != NULL; + int next_start_idx = cur_str_idx; + + err = REG_NOERROR; + cur_state = acquire_init_state_context (&err, mctx, cur_str_idx); + /* An initial state must not be NULL (invalid). */ + if (BE (cur_state == NULL, 0)) + { + assert (err == REG_ESPACE); + return -2; + } + + if (mctx->state_log != NULL) + { + mctx->state_log[cur_str_idx] = cur_state; + + /* Check OP_OPEN_SUBEXP in the initial state in case that we use them + later. E.g. Processing back references. */ + if (BE (dfa->nbackref, 0)) + { + at_init_state = 0; + err = check_subexp_matching_top (mctx, &cur_state->nodes, 0); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (cur_state->has_backref) + { + err = transit_state_bkref (mctx, &cur_state->nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + } + + /* If the RE accepts NULL string. */ + if (BE (cur_state->halt, 0)) + { + if (!cur_state->has_constraint + || check_halt_state_context (mctx, cur_state, cur_str_idx)) + { + if (!fl_longest_match) + return cur_str_idx; + else + { + match_last = cur_str_idx; + match = 1; + } + } + } + + while (!re_string_eoi (&mctx->input)) + { + re_dfastate_t *old_state = cur_state; + int next_char_idx = re_string_cur_idx (&mctx->input) + 1; + + if (BE (next_char_idx >= mctx->input.bufs_len, 0) + || (BE (next_char_idx >= mctx->input.valid_len, 0) + && mctx->input.valid_len < mctx->input.len)) + { + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + { + assert (err == REG_ESPACE); + return -2; + } + } + + cur_state = transit_state (&err, mctx, cur_state); + if (mctx->state_log != NULL) + cur_state = merge_state_with_log (&err, mctx, cur_state); + + if (cur_state == NULL) + { + /* Reached the invalid state or an error. Try to recover a valid + state using the state log, if available and if we have not + already found a valid (even if not the longest) match. */ + if (BE (err != REG_NOERROR, 0)) + return -2; + + if (mctx->state_log == NULL + || (match && !fl_longest_match) + || (cur_state = find_recover_state (&err, mctx)) == NULL) + break; + } + + if (BE (at_init_state, 0)) + { + if (old_state == cur_state) + next_start_idx = next_char_idx; + else + at_init_state = 0; + } + + if (cur_state->halt) + { + /* Reached a halt state. + Check the halt state can satisfy the current context. */ + if (!cur_state->has_constraint + || check_halt_state_context (mctx, cur_state, + re_string_cur_idx (&mctx->input))) + { + /* We found an appropriate halt state. */ + match_last = re_string_cur_idx (&mctx->input); + match = 1; + + /* We found a match, do not modify match_first below. */ + p_match_first = NULL; + if (!fl_longest_match) + break; + } + } + } + + if (p_match_first) + *p_match_first += next_start_idx; + + return match_last; +} + +/* Check NODE match the current context. */ + +static int +internal_function +check_halt_node_context (const re_dfa_t *dfa, int node, unsigned int context) +{ + re_token_type_t type = dfa->nodes[node].type; + unsigned int constraint = dfa->nodes[node].constraint; + if (type != END_OF_RE) + return 0; + if (!constraint) + return 1; + if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context)) + return 0; + return 1; +} + +/* Check the halt state STATE match the current context. + Return 0 if not match, if the node, STATE has, is a halt node and + match the context, return the node. */ + +static int +internal_function +check_halt_state_context (const re_match_context_t *mctx, + const re_dfastate_t *state, int idx) +{ + int i; + unsigned int context; +#ifdef DEBUG + assert (state->halt); +#endif + context = re_string_context_at (&mctx->input, idx, mctx->eflags); + for (i = 0; i < state->nodes.nelem; ++i) + if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context)) + return state->nodes.elems[i]; + return 0; +} + +/* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA + corresponding to the DFA). + Return the destination node, and update EPS_VIA_NODES, return -1 in case + of errors. */ + +static int +internal_function +proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs, + int *pidx, int node, re_node_set *eps_via_nodes, + struct re_fail_stack_t *fs) +{ + const re_dfa_t *const dfa = mctx->dfa; + int i, err; + if (IS_EPSILON_NODE (dfa->nodes[node].type)) + { + re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes; + re_node_set *edests = &dfa->edests[node]; + int dest_node; + err = re_node_set_insert (eps_via_nodes, node); + if (BE (err < 0, 0)) + return -2; + /* Pick up a valid destination, or return -1 if none is found. */ + for (dest_node = -1, i = 0; i < edests->nelem; ++i) + { + int candidate = edests->elems[i]; + if (!re_node_set_contains (cur_nodes, candidate)) + continue; + if (dest_node == -1) + dest_node = candidate; + + else + { + /* In order to avoid infinite loop like "(a*)*", return the second + epsilon-transition if the first was already considered. */ + if (re_node_set_contains (eps_via_nodes, dest_node)) + return candidate; + + /* Otherwise, push the second epsilon-transition on the fail stack. */ + else if (fs != NULL + && push_fail_stack (fs, *pidx, candidate, nregs, regs, + eps_via_nodes)) + return -2; + + /* We know we are going to exit. */ + break; + } + } + return dest_node; + } + else + { + int naccepted = 0; + re_token_type_t type = dfa->nodes[node].type; + +#ifdef RE_ENABLE_I18N + if (dfa->nodes[node].accept_mb) + naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx); + else +#endif /* RE_ENABLE_I18N */ + if (type == OP_BACK_REF) + { + int subexp_idx = dfa->nodes[node].opr.idx + 1; + naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so; + if (fs != NULL) + { + if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1) + return -1; + else if (naccepted) + { + char *buf = (char *) re_string_get_buffer (&mctx->input); + if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx, + naccepted) != 0) + return -1; + } + } + + if (naccepted == 0) + { + int dest_node; + err = re_node_set_insert (eps_via_nodes, node); + if (BE (err < 0, 0)) + return -2; + dest_node = dfa->edests[node].elems[0]; + if (re_node_set_contains (&mctx->state_log[*pidx]->nodes, + dest_node)) + return dest_node; + } + } + + if (naccepted != 0 + || check_node_accept (mctx, dfa->nodes + node, *pidx)) + { + int dest_node = dfa->nexts[node]; + *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted; + if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL + || !re_node_set_contains (&mctx->state_log[*pidx]->nodes, + dest_node))) + return -1; + re_node_set_empty (eps_via_nodes); + return dest_node; + } + } + return -1; +} + +static reg_errcode_t +internal_function +push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node, + int nregs, regmatch_t *regs, re_node_set *eps_via_nodes) +{ + reg_errcode_t err; + int num = fs->num++; + if (fs->num == fs->alloc) + { + struct re_fail_stack_ent_t *new_array; + new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t) + * fs->alloc * 2)); + if (new_array == NULL) + return REG_ESPACE; + fs->alloc *= 2; + fs->stack = new_array; + } + fs->stack[num].idx = str_idx; + fs->stack[num].node = dest_node; + fs->stack[num].regs = re_malloc (regmatch_t, nregs); + if (fs->stack[num].regs == NULL) + return REG_ESPACE; + memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs); + err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes); + return err; +} + +static int +internal_function +pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs, + regmatch_t *regs, re_node_set *eps_via_nodes) +{ + int num = --fs->num; + assert (num >= 0); + *pidx = fs->stack[num].idx; + memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs); + re_node_set_free (eps_via_nodes); + re_free (fs->stack[num].regs); + *eps_via_nodes = fs->stack[num].eps_via_nodes; + return fs->stack[num].node; +} + +/* Set the positions where the subexpressions are starts/ends to registers + PMATCH. + Note: We assume that pmatch[0] is already set, and + pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */ + +static reg_errcode_t +internal_function +set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch, + regmatch_t *pmatch, int fl_backtrack) +{ + const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer; + int idx, cur_node; + re_node_set eps_via_nodes; + struct re_fail_stack_t *fs; + struct re_fail_stack_t fs_body = { 0, 2, NULL }; + regmatch_t *prev_idx_match; + int prev_idx_match_malloced = 0; + +#ifdef DEBUG + assert (nmatch > 1); + assert (mctx->state_log != NULL); +#endif + if (fl_backtrack) + { + fs = &fs_body; + fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc); + if (fs->stack == NULL) + return REG_ESPACE; + } + else + fs = NULL; + + cur_node = dfa->init_node; + re_node_set_init_empty (&eps_via_nodes); + +#ifdef HAVE_ALLOCA + if (__libc_use_alloca (nmatch * sizeof (regmatch_t))) + prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t)); + else +#endif + { + prev_idx_match = re_malloc (regmatch_t, nmatch); + if (prev_idx_match == NULL) + { + free_fail_stack_return (fs); + return REG_ESPACE; + } + prev_idx_match_malloced = 1; + } + memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); + + for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;) + { + update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch); + + if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node) + { + unsigned int reg_idx; + if (fs) + { + for (reg_idx = 0; reg_idx < nmatch; ++reg_idx) + if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1) + break; + if (reg_idx == nmatch) + { + re_node_set_free (&eps_via_nodes); + if (prev_idx_match_malloced) + re_free (prev_idx_match); + return free_fail_stack_return (fs); + } + cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, + &eps_via_nodes); + } + else + { + re_node_set_free (&eps_via_nodes); + if (prev_idx_match_malloced) + re_free (prev_idx_match); + return REG_NOERROR; + } + } + + /* Proceed to next node. */ + cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node, + &eps_via_nodes, fs); + + if (BE (cur_node < 0, 0)) + { + if (BE (cur_node == -2, 0)) + { + re_node_set_free (&eps_via_nodes); + if (prev_idx_match_malloced) + re_free (prev_idx_match); + free_fail_stack_return (fs); + return REG_ESPACE; + } + if (fs) + cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch, + &eps_via_nodes); + else + { + re_node_set_free (&eps_via_nodes); + if (prev_idx_match_malloced) + re_free (prev_idx_match); + return REG_NOMATCH; + } + } + } + re_node_set_free (&eps_via_nodes); + if (prev_idx_match_malloced) + re_free (prev_idx_match); + return free_fail_stack_return (fs); +} + +static reg_errcode_t +internal_function +free_fail_stack_return (struct re_fail_stack_t *fs) +{ + if (fs) + { + int fs_idx; + for (fs_idx = 0; fs_idx < fs->num; ++fs_idx) + { + re_node_set_free (&fs->stack[fs_idx].eps_via_nodes); + re_free (fs->stack[fs_idx].regs); + } + re_free (fs->stack); + } + return REG_NOERROR; +} + +static void +internal_function +update_regs (const re_dfa_t *dfa, regmatch_t *pmatch, + regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch) +{ + int type = dfa->nodes[cur_node].type; + if (type == OP_OPEN_SUBEXP) + { + int reg_num = dfa->nodes[cur_node].opr.idx + 1; + + /* We are at the first node of this sub expression. */ + if (reg_num < nmatch) + { + pmatch[reg_num].rm_so = cur_idx; + pmatch[reg_num].rm_eo = -1; + } + } + else if (type == OP_CLOSE_SUBEXP) + { + int reg_num = dfa->nodes[cur_node].opr.idx + 1; + if (reg_num < nmatch) + { + /* We are at the last node of this sub expression. */ + if (pmatch[reg_num].rm_so < cur_idx) + { + pmatch[reg_num].rm_eo = cur_idx; + /* This is a non-empty match or we are not inside an optional + subexpression. Accept this right away. */ + memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch); + } + else + { + if (dfa->nodes[cur_node].opt_subexp + && prev_idx_match[reg_num].rm_so != -1) + /* We transited through an empty match for an optional + subexpression, like (a?)*, and this is not the subexp's + first match. Copy back the old content of the registers + so that matches of an inner subexpression are undone as + well, like in ((a?))*. */ + memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch); + else + /* We completed a subexpression, but it may be part of + an optional one, so do not update PREV_IDX_MATCH. */ + pmatch[reg_num].rm_eo = cur_idx; + } + } + } +} + +/* This function checks the STATE_LOG from the SCTX->last_str_idx to 0 + and sift the nodes in each states according to the following rules. + Updated state_log will be wrote to STATE_LOG. + + Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if... + 1. When STR_IDX == MATCH_LAST(the last index in the state_log): + If `a' isn't the LAST_NODE and `a' can't epsilon transit to + the LAST_NODE, we throw away the node `a'. + 2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts + string `s' and transit to `b': + i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw + away the node `a'. + ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is + thrown away, we throw away the node `a'. + 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b': + i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the + node `a'. + ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away, + we throw away the node `a'. */ + +#define STATE_NODE_CONTAINS(state,node) \ + ((state) != NULL && re_node_set_contains (&(state)->nodes, node)) + +static reg_errcode_t +internal_function +sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx) +{ + reg_errcode_t err; + int null_cnt = 0; + int str_idx = sctx->last_str_idx; + re_node_set cur_dest; + +#ifdef DEBUG + assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL); +#endif + + /* Build sifted state_log[str_idx]. It has the nodes which can epsilon + transit to the last_node and the last_node itself. */ + err = re_node_set_init_1 (&cur_dest, sctx->last_node); + if (BE (err != REG_NOERROR, 0)) + return err; + err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* Then check each states in the state_log. */ + while (str_idx > 0) + { + /* Update counters. */ + null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0; + if (null_cnt > mctx->max_mb_elem_len) + { + memset (sctx->sifted_states, '\0', + sizeof (re_dfastate_t *) * str_idx); + re_node_set_free (&cur_dest); + return REG_NOERROR; + } + re_node_set_empty (&cur_dest); + --str_idx; + + if (mctx->state_log[str_idx]) + { + err = build_sifted_states (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + + /* Add all the nodes which satisfy the following conditions: + - It can epsilon transit to a node in CUR_DEST. + - It is in CUR_SRC. + And update state_log. */ + err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + err = REG_NOERROR; + free_return: + re_node_set_free (&cur_dest); + return err; +} + +static reg_errcode_t +internal_function +build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx, + int str_idx, re_node_set *cur_dest) +{ + const re_dfa_t *const dfa = mctx->dfa; + const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes; + int i; + + /* Then build the next sifted state. + We build the next sifted state on `cur_dest', and update + `sifted_states[str_idx]' with `cur_dest'. + Note: + `cur_dest' is the sifted state from `state_log[str_idx + 1]'. + `cur_src' points the node_set of the old `state_log[str_idx]' + (with the epsilon nodes pre-filtered out). */ + for (i = 0; i < cur_src->nelem; i++) + { + int prev_node = cur_src->elems[i]; + int naccepted = 0; + int ret; + +#ifdef DEBUG + re_token_type_t type = dfa->nodes[prev_node].type; + assert (!IS_EPSILON_NODE (type)); +#endif +#ifdef RE_ENABLE_I18N + /* If the node may accept `multi byte'. */ + if (dfa->nodes[prev_node].accept_mb) + naccepted = sift_states_iter_mb (mctx, sctx, prev_node, + str_idx, sctx->last_str_idx); +#endif /* RE_ENABLE_I18N */ + + /* We don't check backreferences here. + See update_cur_sifted_state(). */ + if (!naccepted + && check_node_accept (mctx, dfa->nodes + prev_node, str_idx) + && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1], + dfa->nexts[prev_node])) + naccepted = 1; + + if (naccepted == 0) + continue; + + if (sctx->limits.nelem) + { + int to_idx = str_idx + naccepted; + if (check_dst_limits (mctx, &sctx->limits, + dfa->nexts[prev_node], to_idx, + prev_node, str_idx)) + continue; + } + ret = re_node_set_insert (cur_dest, prev_node); + if (BE (ret == -1, 0)) + return REG_ESPACE; + } + + return REG_NOERROR; +} + +/* Helper functions. */ + +static reg_errcode_t +internal_function +clean_state_log_if_needed (re_match_context_t *mctx, int next_state_log_idx) +{ + int top = mctx->state_log_top; + + if (next_state_log_idx >= mctx->input.bufs_len + || (next_state_log_idx >= mctx->input.valid_len + && mctx->input.valid_len < mctx->input.len)) + { + reg_errcode_t err; + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (top < next_state_log_idx) + { + memset (mctx->state_log + top + 1, '\0', + sizeof (re_dfastate_t *) * (next_state_log_idx - top)); + mctx->state_log_top = next_state_log_idx; + } + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst, + re_dfastate_t **src, int num) +{ + int st_idx; + reg_errcode_t err; + for (st_idx = 0; st_idx < num; ++st_idx) + { + if (dst[st_idx] == NULL) + dst[st_idx] = src[st_idx]; + else if (src[st_idx] != NULL) + { + re_node_set merged_set; + err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes, + &src[st_idx]->nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + dst[st_idx] = re_acquire_state (&err, dfa, &merged_set); + re_node_set_free (&merged_set); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +update_cur_sifted_state (const re_match_context_t *mctx, + re_sift_context_t *sctx, int str_idx, + re_node_set *dest_nodes) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err = REG_NOERROR; + const re_node_set *candidates; + candidates = ((mctx->state_log[str_idx] == NULL) ? NULL + : &mctx->state_log[str_idx]->nodes); + + if (dest_nodes->nelem == 0) + sctx->sifted_states[str_idx] = NULL; + else + { + if (candidates) + { + /* At first, add the nodes which can epsilon transit to a node in + DEST_NODE. */ + err = add_epsilon_src_nodes (dfa, dest_nodes, candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + + /* Then, check the limitations in the current sift_context. */ + if (sctx->limits.nelem) + { + err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits, + mctx->bkref_ents, str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + + sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (candidates && mctx->state_log[str_idx]->has_backref) + { + err = sift_states_bkref (mctx, sctx, str_idx, candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes, + const re_node_set *candidates) +{ + reg_errcode_t err = REG_NOERROR; + int i; + + re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + + if (!state->inveclosure.alloc) + { + err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem); + if (BE (err != REG_NOERROR, 0)) + return REG_ESPACE; + for (i = 0; i < dest_nodes->nelem; i++) + { + err = re_node_set_merge (&state->inveclosure, + dfa->inveclosures + dest_nodes->elems[i]); + if (BE (err != REG_NOERROR, 0)) + return REG_ESPACE; + } + } + return re_node_set_add_intersect (dest_nodes, candidates, + &state->inveclosure); +} + +static reg_errcode_t +internal_function +sub_epsilon_src_nodes (const re_dfa_t *dfa, int node, re_node_set *dest_nodes, + const re_node_set *candidates) +{ + int ecl_idx; + reg_errcode_t err; + re_node_set *inv_eclosure = dfa->inveclosures + node; + re_node_set except_nodes; + re_node_set_init_empty (&except_nodes); + for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) + { + int cur_node = inv_eclosure->elems[ecl_idx]; + if (cur_node == node) + continue; + if (IS_EPSILON_NODE (dfa->nodes[cur_node].type)) + { + int edst1 = dfa->edests[cur_node].elems[0]; + int edst2 = ((dfa->edests[cur_node].nelem > 1) + ? dfa->edests[cur_node].elems[1] : -1); + if ((!re_node_set_contains (inv_eclosure, edst1) + && re_node_set_contains (dest_nodes, edst1)) + || (edst2 > 0 + && !re_node_set_contains (inv_eclosure, edst2) + && re_node_set_contains (dest_nodes, edst2))) + { + err = re_node_set_add_intersect (&except_nodes, candidates, + dfa->inveclosures + cur_node); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&except_nodes); + return err; + } + } + } + } + for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx) + { + int cur_node = inv_eclosure->elems[ecl_idx]; + if (!re_node_set_contains (&except_nodes, cur_node)) + { + int idx = re_node_set_contains (dest_nodes, cur_node) - 1; + re_node_set_remove_at (dest_nodes, idx); + } + } + re_node_set_free (&except_nodes); + return REG_NOERROR; +} + +static int +internal_function +check_dst_limits (const re_match_context_t *mctx, re_node_set *limits, + int dst_node, int dst_idx, int src_node, int src_idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + int lim_idx, src_pos, dst_pos; + + int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx); + int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx); + for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) + { + int subexp_idx; + struct re_backref_cache_entry *ent; + ent = mctx->bkref_ents + limits->elems[lim_idx]; + subexp_idx = dfa->nodes[ent->node].opr.idx; + + dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], + subexp_idx, dst_node, dst_idx, + dst_bkref_idx); + src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx], + subexp_idx, src_node, src_idx, + src_bkref_idx); + + /* In case of: + ( ) + ( ) + ( ) */ + if (src_pos == dst_pos) + continue; /* This is unrelated limitation. */ + else + return 1; + } + return 0; +} + +static int +internal_function +check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries, + int subexp_idx, int from_node, int bkref_idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + const re_node_set *eclosures = dfa->eclosures + from_node; + int node_idx; + + /* Else, we are on the boundary: examine the nodes on the epsilon + closure. */ + for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx) + { + int node = eclosures->elems[node_idx]; + switch (dfa->nodes[node].type) + { + case OP_BACK_REF: + if (bkref_idx != -1) + { + struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx; + do + { + int dst, cpos; + + if (ent->node != node) + continue; + + if (subexp_idx < BITSET_WORD_BITS + && !(ent->eps_reachable_subexps_map + & ((bitset_word_t) 1 << subexp_idx))) + continue; + + /* Recurse trying to reach the OP_OPEN_SUBEXP and + OP_CLOSE_SUBEXP cases below. But, if the + destination node is the same node as the source + node, don't recurse because it would cause an + infinite loop: a regex that exhibits this behavior + is ()\1*\1* */ + dst = dfa->edests[node].elems[0]; + if (dst == from_node) + { + if (boundaries & 1) + return -1; + else /* if (boundaries & 2) */ + return 0; + } + + cpos = + check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, + dst, bkref_idx); + if (cpos == -1 /* && (boundaries & 1) */) + return -1; + if (cpos == 0 && (boundaries & 2)) + return 0; + + if (subexp_idx < BITSET_WORD_BITS) + ent->eps_reachable_subexps_map + &= ~((bitset_word_t) 1 << subexp_idx); + } + while (ent++->more); + } + break; + + case OP_OPEN_SUBEXP: + if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx) + return -1; + break; + + case OP_CLOSE_SUBEXP: + if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx) + return 0; + break; + + default: + break; + } + } + + return (boundaries & 2) ? 1 : 0; +} + +static int +internal_function +check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit, + int subexp_idx, int from_node, int str_idx, + int bkref_idx) +{ + struct re_backref_cache_entry *lim = mctx->bkref_ents + limit; + int boundaries; + + /* If we are outside the range of the subexpression, return -1 or 1. */ + if (str_idx < lim->subexp_from) + return -1; + + if (lim->subexp_to < str_idx) + return 1; + + /* If we are within the subexpression, return 0. */ + boundaries = (str_idx == lim->subexp_from); + boundaries |= (str_idx == lim->subexp_to) << 1; + if (boundaries == 0) + return 0; + + /* Else, examine epsilon closure. */ + return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx, + from_node, bkref_idx); +} + +/* Check the limitations of sub expressions LIMITS, and remove the nodes + which are against limitations from DEST_NODES. */ + +static reg_errcode_t +internal_function +check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes, + const re_node_set *candidates, re_node_set *limits, + struct re_backref_cache_entry *bkref_ents, int str_idx) +{ + reg_errcode_t err; + int node_idx, lim_idx; + + for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx) + { + int subexp_idx; + struct re_backref_cache_entry *ent; + ent = bkref_ents + limits->elems[lim_idx]; + + if (str_idx <= ent->subexp_from || ent->str_idx < str_idx) + continue; /* This is unrelated limitation. */ + + subexp_idx = dfa->nodes[ent->node].opr.idx; + if (ent->subexp_to == str_idx) + { + int ops_node = -1; + int cls_node = -1; + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + re_token_type_t type = dfa->nodes[node].type; + if (type == OP_OPEN_SUBEXP + && subexp_idx == dfa->nodes[node].opr.idx) + ops_node = node; + else if (type == OP_CLOSE_SUBEXP + && subexp_idx == dfa->nodes[node].opr.idx) + cls_node = node; + } + + /* Check the limitation of the open subexpression. */ + /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */ + if (ops_node >= 0) + { + err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + + /* Check the limitation of the close subexpression. */ + if (cls_node >= 0) + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + if (!re_node_set_contains (dfa->inveclosures + node, + cls_node) + && !re_node_set_contains (dfa->eclosures + node, + cls_node)) + { + /* It is against this limitation. + Remove it form the current sifted state. */ + err = sub_epsilon_src_nodes (dfa, node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + --node_idx; + } + } + } + else /* (ent->subexp_to != str_idx) */ + { + for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx) + { + int node = dest_nodes->elems[node_idx]; + re_token_type_t type = dfa->nodes[node].type; + if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP) + { + if (subexp_idx != dfa->nodes[node].opr.idx) + continue; + /* It is against this limitation. + Remove it form the current sifted state. */ + err = sub_epsilon_src_nodes (dfa, node, dest_nodes, + candidates); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + } + } + return REG_NOERROR; +} + +static reg_errcode_t +internal_function +sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, + int str_idx, const re_node_set *candidates) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int node_idx, node; + re_sift_context_t local_sctx; + int first_idx = search_cur_bkref_entry (mctx, str_idx); + + if (first_idx == -1) + return REG_NOERROR; + + local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */ + + for (node_idx = 0; node_idx < candidates->nelem; ++node_idx) + { + int enabled_idx; + re_token_type_t type; + struct re_backref_cache_entry *entry; + node = candidates->elems[node_idx]; + type = dfa->nodes[node].type; + /* Avoid infinite loop for the REs like "()\1+". */ + if (node == sctx->last_node && str_idx == sctx->last_str_idx) + continue; + if (type != OP_BACK_REF) + continue; + + entry = mctx->bkref_ents + first_idx; + enabled_idx = first_idx; + do + { + int subexp_len; + int to_idx; + int dst_node; + int ret; + re_dfastate_t *cur_state; + + if (entry->node != node) + continue; + subexp_len = entry->subexp_to - entry->subexp_from; + to_idx = str_idx + subexp_len; + dst_node = (subexp_len ? dfa->nexts[node] + : dfa->edests[node].elems[0]); + + if (to_idx > sctx->last_str_idx + || sctx->sifted_states[to_idx] == NULL + || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node) + || check_dst_limits (mctx, &sctx->limits, node, + str_idx, dst_node, to_idx)) + continue; + + if (local_sctx.sifted_states == NULL) + { + local_sctx = *sctx; + err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + local_sctx.last_node = node; + local_sctx.last_str_idx = str_idx; + ret = re_node_set_insert (&local_sctx.limits, enabled_idx); + if (BE (ret < 0, 0)) + { + err = REG_ESPACE; + goto free_return; + } + cur_state = local_sctx.sifted_states[str_idx]; + err = sift_states_backward (mctx, &local_sctx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + if (sctx->limited_states != NULL) + { + err = merge_state_array (dfa, sctx->limited_states, + local_sctx.sifted_states, + str_idx + 1); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + local_sctx.sifted_states[str_idx] = cur_state; + re_node_set_remove (&local_sctx.limits, enabled_idx); + + /* mctx->bkref_ents may have changed, reload the pointer. */ + entry = mctx->bkref_ents + enabled_idx; + } + while (enabled_idx++, entry++->more); + } + err = REG_NOERROR; + free_return: + if (local_sctx.sifted_states != NULL) + { + re_node_set_free (&local_sctx.limits); + } + + return err; +} + + +#ifdef RE_ENABLE_I18N +static int +internal_function +sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx, + int node_idx, int str_idx, int max_str_idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + int naccepted; + /* Check the node can accept `multi byte'. */ + naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx); + if (naccepted > 0 && str_idx + naccepted <= max_str_idx && + !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted], + dfa->nexts[node_idx])) + /* The node can't accept the `multi byte', or the + destination was already thrown away, then the node + could't accept the current input `multi byte'. */ + naccepted = 0; + /* Otherwise, it is sure that the node could accept + `naccepted' bytes input. */ + return naccepted; +} +#endif /* RE_ENABLE_I18N */ + + +/* Functions for state transition. */ + +/* Return the next state to which the current state STATE will transit by + accepting the current input byte, and update STATE_LOG if necessary. + If STATE can accept a multibyte char/collating element/back reference + update the destination of STATE_LOG. */ + +static re_dfastate_t * +internal_function +transit_state (reg_errcode_t *err, re_match_context_t *mctx, + re_dfastate_t *state) +{ + re_dfastate_t **trtable; + unsigned char ch; + +#ifdef RE_ENABLE_I18N + /* If the current state can accept multibyte. */ + if (BE (state->accept_mb, 0)) + { + *err = transit_state_mb (mctx, state); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + } +#endif /* RE_ENABLE_I18N */ + + /* Then decide the next state with the single byte. */ +#if 0 + if (0) + /* don't use transition table */ + return transit_state_sb (err, mctx, state); +#endif + + /* Use transition table */ + ch = re_string_fetch_byte (&mctx->input); + for (;;) + { + trtable = state->trtable; + if (BE (trtable != NULL, 1)) + return trtable[ch]; + + trtable = state->word_trtable; + if (BE (trtable != NULL, 1)) + { + unsigned int context; + context + = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input) - 1, + mctx->eflags); + if (IS_WORD_CONTEXT (context)) + return trtable[ch + SBC_MAX]; + else + return trtable[ch]; + } + + if (!build_trtable (mctx->dfa, state)) + { + *err = REG_ESPACE; + return NULL; + } + + /* Retry, we now have a transition table. */ + } +} + +/* Update the state_log if we need */ +re_dfastate_t * +internal_function +merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx, + re_dfastate_t *next_state) +{ + const re_dfa_t *const dfa = mctx->dfa; + int cur_idx = re_string_cur_idx (&mctx->input); + + if (cur_idx > mctx->state_log_top) + { + mctx->state_log[cur_idx] = next_state; + mctx->state_log_top = cur_idx; + } + else if (mctx->state_log[cur_idx] == 0) + { + mctx->state_log[cur_idx] = next_state; + } + else + { + re_dfastate_t *pstate; + unsigned int context; + re_node_set next_nodes, *log_nodes, *table_nodes = NULL; + /* If (state_log[cur_idx] != 0), it implies that cur_idx is + the destination of a multibyte char/collating element/ + back reference. Then the next state is the union set of + these destinations and the results of the transition table. */ + pstate = mctx->state_log[cur_idx]; + log_nodes = pstate->entrance_nodes; + if (next_state != NULL) + { + table_nodes = next_state->entrance_nodes; + *err = re_node_set_init_union (&next_nodes, table_nodes, + log_nodes); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + } + else + next_nodes = *log_nodes; + /* Note: We already add the nodes of the initial state, + then we don't need to add them here. */ + + context = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input) - 1, + mctx->eflags); + next_state = mctx->state_log[cur_idx] + = re_acquire_state_context (err, dfa, &next_nodes, context); + /* We don't need to check errors here, since the return value of + this function is next_state and ERR is already set. */ + + if (table_nodes != NULL) + re_node_set_free (&next_nodes); + } + + if (BE (dfa->nbackref, 0) && next_state != NULL) + { + /* Check OP_OPEN_SUBEXP in the current state in case that we use them + later. We must check them here, since the back references in the + next state might use them. */ + *err = check_subexp_matching_top (mctx, &next_state->nodes, + cur_idx); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + + /* If the next state has back references. */ + if (next_state->has_backref) + { + *err = transit_state_bkref (mctx, &next_state->nodes); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + next_state = mctx->state_log[cur_idx]; + } + } + + return next_state; +} + +/* Skip bytes in the input that correspond to part of a + multi-byte match, then look in the log for a state + from which to restart matching. */ +re_dfastate_t * +internal_function +find_recover_state (reg_errcode_t *err, re_match_context_t *mctx) +{ + re_dfastate_t *cur_state; + do + { + int max = mctx->state_log_top; + int cur_str_idx = re_string_cur_idx (&mctx->input); + + do + { + if (++cur_str_idx > max) + return NULL; + re_string_skip_bytes (&mctx->input, 1); + } + while (mctx->state_log[cur_str_idx] == NULL); + + cur_state = merge_state_with_log (err, mctx, NULL); + } + while (*err == REG_NOERROR && cur_state == NULL); + return cur_state; +} + +/* Helper functions for transit_state. */ + +/* From the node set CUR_NODES, pick up the nodes whose types are + OP_OPEN_SUBEXP and which have corresponding back references in the regular + expression. And register them to use them later for evaluating the + correspoding back references. */ + +static reg_errcode_t +internal_function +check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes, + int str_idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + int node_idx; + reg_errcode_t err; + + /* TODO: This isn't efficient. + Because there might be more than one nodes whose types are + OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all + nodes. + E.g. RE: (a){2} */ + for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx) + { + int node = cur_nodes->elems[node_idx]; + if (dfa->nodes[node].type == OP_OPEN_SUBEXP + && dfa->nodes[node].opr.idx < BITSET_WORD_BITS + && (dfa->used_bkref_map + & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx))) + { + err = match_ctx_add_subtop (mctx, node, str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + } + } + return REG_NOERROR; +} + +#if 0 +/* Return the next state to which the current state STATE will transit by + accepting the current input byte. */ + +static re_dfastate_t * +transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx, + re_dfastate_t *state) +{ + const re_dfa_t *const dfa = mctx->dfa; + re_node_set next_nodes; + re_dfastate_t *next_state; + int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input); + unsigned int context; + + *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1); + if (BE (*err != REG_NOERROR, 0)) + return NULL; + for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt) + { + int cur_node = state->nodes.elems[node_cnt]; + if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx)) + { + *err = re_node_set_merge (&next_nodes, + dfa->eclosures + dfa->nexts[cur_node]); + if (BE (*err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return NULL; + } + } + } + context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags); + next_state = re_acquire_state_context (err, dfa, &next_nodes, context); + /* We don't need to check errors here, since the return value of + this function is next_state and ERR is already set. */ + + re_node_set_free (&next_nodes); + re_string_skip_bytes (&mctx->input, 1); + return next_state; +} +#endif + +#ifdef RE_ENABLE_I18N +static reg_errcode_t +internal_function +transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int i; + + for (i = 0; i < pstate->nodes.nelem; ++i) + { + re_node_set dest_nodes, *new_nodes; + int cur_node_idx = pstate->nodes.elems[i]; + int naccepted, dest_idx; + unsigned int context; + re_dfastate_t *dest_state; + + if (!dfa->nodes[cur_node_idx].accept_mb) + continue; + + if (dfa->nodes[cur_node_idx].constraint) + { + context = re_string_context_at (&mctx->input, + re_string_cur_idx (&mctx->input), + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint, + context)) + continue; + } + + /* How many bytes the node can accept? */ + naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input, + re_string_cur_idx (&mctx->input)); + if (naccepted == 0) + continue; + + /* The node can accepts `naccepted' bytes. */ + dest_idx = re_string_cur_idx (&mctx->input) + naccepted; + mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted + : mctx->max_mb_elem_len); + err = clean_state_log_if_needed (mctx, dest_idx); + if (BE (err != REG_NOERROR, 0)) + return err; +#ifdef DEBUG + assert (dfa->nexts[cur_node_idx] != -1); +#endif + new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx]; + + dest_state = mctx->state_log[dest_idx]; + if (dest_state == NULL) + dest_nodes = *new_nodes; + else + { + err = re_node_set_init_union (&dest_nodes, + dest_state->entrance_nodes, new_nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + context = re_string_context_at (&mctx->input, dest_idx - 1, + mctx->eflags); + mctx->state_log[dest_idx] + = re_acquire_state_context (&err, dfa, &dest_nodes, context); + if (dest_state != NULL) + re_node_set_free (&dest_nodes); + if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0)) + return err; + } + return REG_NOERROR; +} +#endif /* RE_ENABLE_I18N */ + +static reg_errcode_t +internal_function +transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int i; + int cur_str_idx = re_string_cur_idx (&mctx->input); + + for (i = 0; i < nodes->nelem; ++i) + { + int dest_str_idx, prev_nelem, bkc_idx; + int node_idx = nodes->elems[i]; + unsigned int context; + const re_token_t *node = dfa->nodes + node_idx; + re_node_set *new_dest_nodes; + + /* Check whether `node' is a backreference or not. */ + if (node->type != OP_BACK_REF) + continue; + + if (node->constraint) + { + context = re_string_context_at (&mctx->input, cur_str_idx, + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) + continue; + } + + /* `node' is a backreference. + Check the substring which the substring matched. */ + bkc_idx = mctx->nbkref_ents; + err = get_subexp (mctx, node_idx, cur_str_idx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + + /* And add the epsilon closures (which is `new_dest_nodes') of + the backreference to appropriate state_log. */ +#ifdef DEBUG + assert (dfa->nexts[node_idx] != -1); +#endif + for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx) + { + int subexp_len; + re_dfastate_t *dest_state; + struct re_backref_cache_entry *bkref_ent; + bkref_ent = mctx->bkref_ents + bkc_idx; + if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx) + continue; + subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from; + new_dest_nodes = (subexp_len == 0 + ? dfa->eclosures + dfa->edests[node_idx].elems[0] + : dfa->eclosures + dfa->nexts[node_idx]); + dest_str_idx = (cur_str_idx + bkref_ent->subexp_to + - bkref_ent->subexp_from); + context = re_string_context_at (&mctx->input, dest_str_idx - 1, + mctx->eflags); + dest_state = mctx->state_log[dest_str_idx]; + prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0 + : mctx->state_log[cur_str_idx]->nodes.nelem); + /* Add `new_dest_node' to state_log. */ + if (dest_state == NULL) + { + mctx->state_log[dest_str_idx] + = re_acquire_state_context (&err, dfa, new_dest_nodes, + context); + if (BE (mctx->state_log[dest_str_idx] == NULL + && err != REG_NOERROR, 0)) + goto free_return; + } + else + { + re_node_set dest_nodes; + err = re_node_set_init_union (&dest_nodes, + dest_state->entrance_nodes, + new_dest_nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&dest_nodes); + goto free_return; + } + mctx->state_log[dest_str_idx] + = re_acquire_state_context (&err, dfa, &dest_nodes, context); + re_node_set_free (&dest_nodes); + if (BE (mctx->state_log[dest_str_idx] == NULL + && err != REG_NOERROR, 0)) + goto free_return; + } + /* We need to check recursively if the backreference can epsilon + transit. */ + if (subexp_len == 0 + && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem) + { + err = check_subexp_matching_top (mctx, new_dest_nodes, + cur_str_idx); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + err = transit_state_bkref (mctx, new_dest_nodes); + if (BE (err != REG_NOERROR, 0)) + goto free_return; + } + } + } + err = REG_NOERROR; + free_return: + return err; +} + +/* Enumerate all the candidates which the backreference BKREF_NODE can match + at BKREF_STR_IDX, and register them by match_ctx_add_entry(). + Note that we might collect inappropriate candidates here. + However, the cost of checking them strictly here is too high, then we + delay these checking for prune_impossible_nodes(). */ + +static reg_errcode_t +internal_function +get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx) +{ + const re_dfa_t *const dfa = mctx->dfa; + int subexp_num, sub_top_idx; + const char *buf = (const char *) re_string_get_buffer (&mctx->input); + /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */ + int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx); + if (cache_idx != -1) + { + const struct re_backref_cache_entry *entry + = mctx->bkref_ents + cache_idx; + do + if (entry->node == bkref_node) + return REG_NOERROR; /* We already checked it. */ + while (entry++->more); + } + + subexp_num = dfa->nodes[bkref_node].opr.idx; + + /* For each sub expression */ + for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx) + { + reg_errcode_t err; + re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx]; + re_sub_match_last_t *sub_last; + int sub_last_idx, sl_str, bkref_str_off; + + if (dfa->nodes[sub_top->node].opr.idx != subexp_num) + continue; /* It isn't related. */ + + sl_str = sub_top->str_idx; + bkref_str_off = bkref_str_idx; + /* At first, check the last node of sub expressions we already + evaluated. */ + for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx) + { + int sl_str_diff; + sub_last = sub_top->lasts[sub_last_idx]; + sl_str_diff = sub_last->str_idx - sl_str; + /* The matched string by the sub expression match with the substring + at the back reference? */ + if (sl_str_diff > 0) + { + if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0)) + { + /* Not enough chars for a successful match. */ + if (bkref_str_off + sl_str_diff > mctx->input.len) + break; + + err = clean_state_log_if_needed (mctx, + bkref_str_off + + sl_str_diff); + if (BE (err != REG_NOERROR, 0)) + return err; + buf = (const char *) re_string_get_buffer (&mctx->input); + } + if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0) + /* We don't need to search this sub expression any more. */ + break; + } + bkref_str_off += sl_str_diff; + sl_str += sl_str_diff; + err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, + bkref_str_idx); + + /* Reload buf, since the preceding call might have reallocated + the buffer. */ + buf = (const char *) re_string_get_buffer (&mctx->input); + + if (err == REG_NOMATCH) + continue; + if (BE (err != REG_NOERROR, 0)) + return err; + } + + if (sub_last_idx < sub_top->nlasts) + continue; + if (sub_last_idx > 0) + ++sl_str; + /* Then, search for the other last nodes of the sub expression. */ + for (; sl_str <= bkref_str_idx; ++sl_str) + { + int cls_node, sl_str_off; + const re_node_set *nodes; + sl_str_off = sl_str - sub_top->str_idx; + /* The matched string by the sub expression match with the substring + at the back reference? */ + if (sl_str_off > 0) + { + if (BE (bkref_str_off >= mctx->input.valid_len, 0)) + { + /* If we are at the end of the input, we cannot match. */ + if (bkref_str_off >= mctx->input.len) + break; + + err = extend_buffers (mctx); + if (BE (err != REG_NOERROR, 0)) + return err; + + buf = (const char *) re_string_get_buffer (&mctx->input); + } + if (buf [bkref_str_off++] != buf[sl_str - 1]) + break; /* We don't need to search this sub expression + any more. */ + } + if (mctx->state_log[sl_str] == NULL) + continue; + /* Does this state have a ')' of the sub expression? */ + nodes = &mctx->state_log[sl_str]->nodes; + cls_node = find_subexp_node (dfa, nodes, subexp_num, + OP_CLOSE_SUBEXP); + if (cls_node == -1) + continue; /* No. */ + if (sub_top->path == NULL) + { + sub_top->path = calloc (sizeof (state_array_t), + sl_str - sub_top->str_idx + 1); + if (sub_top->path == NULL) + return REG_ESPACE; + } + /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node + in the current context? */ + err = check_arrival (mctx, sub_top->path, sub_top->node, + sub_top->str_idx, cls_node, sl_str, + OP_CLOSE_SUBEXP); + if (err == REG_NOMATCH) + continue; + if (BE (err != REG_NOERROR, 0)) + return err; + sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str); + if (BE (sub_last == NULL, 0)) + return REG_ESPACE; + err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node, + bkref_str_idx); + if (err == REG_NOMATCH) + continue; + } + } + return REG_NOERROR; +} + +/* Helper functions for get_subexp(). */ + +/* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR. + If it can arrive, register the sub expression expressed with SUB_TOP + and SUB_LAST. */ + +static reg_errcode_t +internal_function +get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top, + re_sub_match_last_t *sub_last, int bkref_node, int bkref_str) +{ + reg_errcode_t err; + int to_idx; + /* Can the subexpression arrive the back reference? */ + err = check_arrival (mctx, &sub_last->path, sub_last->node, + sub_last->str_idx, bkref_node, bkref_str, + OP_OPEN_SUBEXP); + if (err != REG_NOERROR) + return err; + err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx, + sub_last->str_idx); + if (BE (err != REG_NOERROR, 0)) + return err; + to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx; + return clean_state_log_if_needed (mctx, to_idx); +} + +/* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX. + Search '(' if FL_OPEN, or search ')' otherwise. + TODO: This function isn't efficient... + Because there might be more than one nodes whose types are + OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all + nodes. + E.g. RE: (a){2} */ + +static int +internal_function +find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes, + int subexp_idx, int type) +{ + int cls_idx; + for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx) + { + int cls_node = nodes->elems[cls_idx]; + const re_token_t *node = dfa->nodes + cls_node; + if (node->type == type + && node->opr.idx == subexp_idx) + return cls_node; + } + return -1; +} + +/* Check whether the node TOP_NODE at TOP_STR can arrive to the node + LAST_NODE at LAST_STR. We record the path onto PATH since it will be + heavily reused. + Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */ + +static reg_errcode_t +internal_function +check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node, + int top_str, int last_node, int last_str, int type) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err = REG_NOERROR; + int subexp_num, backup_cur_idx, str_idx, null_cnt; + re_dfastate_t *cur_state = NULL; + re_node_set *cur_nodes, next_nodes; + re_dfastate_t **backup_state_log; + unsigned int context; + + subexp_num = dfa->nodes[top_node].opr.idx; + /* Extend the buffer if we need. */ + if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0)) + { + re_dfastate_t **new_array; + int old_alloc = path->alloc; + path->alloc += last_str + mctx->max_mb_elem_len + 1; + new_array = re_realloc (path->array, re_dfastate_t *, path->alloc); + if (BE (new_array == NULL, 0)) + { + path->alloc = old_alloc; + return REG_ESPACE; + } + path->array = new_array; + memset (new_array + old_alloc, '\0', + sizeof (re_dfastate_t *) * (path->alloc - old_alloc)); + } + + str_idx = path->next_idx ? path->next_idx : top_str; + + /* Temporary modify MCTX. */ + backup_state_log = mctx->state_log; + backup_cur_idx = mctx->input.cur_idx; + mctx->state_log = path->array; + mctx->input.cur_idx = str_idx; + + /* Setup initial node set. */ + context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); + if (str_idx == top_str) + { + err = re_node_set_init_1 (&next_nodes, top_node); + if (BE (err != REG_NOERROR, 0)) + return err; + err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + else + { + cur_state = mctx->state_log[str_idx]; + if (cur_state && cur_state->has_backref) + { + err = re_node_set_init_copy (&next_nodes, &cur_state->nodes); + if (BE (err != REG_NOERROR, 0)) + return err; + } + else + re_node_set_init_empty (&next_nodes); + } + if (str_idx == top_str || (cur_state && cur_state->has_backref)) + { + if (next_nodes.nelem) + { + err = expand_bkref_cache (mctx, &next_nodes, str_idx, + subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); + if (BE (cur_state == NULL && err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + mctx->state_log[str_idx] = cur_state; + } + + for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;) + { + re_node_set_empty (&next_nodes); + if (mctx->state_log[str_idx + 1]) + { + err = re_node_set_merge (&next_nodes, + &mctx->state_log[str_idx + 1]->nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + if (cur_state) + { + err = check_arrival_add_next_nodes (mctx, str_idx, + &cur_state->non_eps_nodes, + &next_nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + ++str_idx; + if (next_nodes.nelem) + { + err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + err = expand_bkref_cache (mctx, &next_nodes, str_idx, + subexp_num, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + } + context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags); + cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context); + if (BE (cur_state == NULL && err != REG_NOERROR, 0)) + { + re_node_set_free (&next_nodes); + return err; + } + mctx->state_log[str_idx] = cur_state; + null_cnt = cur_state == NULL ? null_cnt + 1 : 0; + } + re_node_set_free (&next_nodes); + cur_nodes = (mctx->state_log[last_str] == NULL ? NULL + : &mctx->state_log[last_str]->nodes); + path->next_idx = str_idx; + + /* Fix MCTX. */ + mctx->state_log = backup_state_log; + mctx->input.cur_idx = backup_cur_idx; + + /* Then check the current node set has the node LAST_NODE. */ + if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node)) + return REG_NOERROR; + + return REG_NOMATCH; +} + +/* Helper functions for check_arrival. */ + +/* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them + to NEXT_NODES. + TODO: This function is similar to the functions transit_state*(), + however this function has many additional works. + Can't we unify them? */ + +static reg_errcode_t +internal_function +check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx, + re_node_set *cur_nodes, re_node_set *next_nodes) +{ + const re_dfa_t *const dfa = mctx->dfa; + int result; + int cur_idx; +#ifdef RE_ENABLE_I18N + reg_errcode_t err = REG_NOERROR; +#endif + re_node_set union_set; + re_node_set_init_empty (&union_set); + for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx) + { + int naccepted = 0; + int cur_node = cur_nodes->elems[cur_idx]; +#ifdef DEBUG + re_token_type_t type = dfa->nodes[cur_node].type; + assert (!IS_EPSILON_NODE (type)); +#endif +#ifdef RE_ENABLE_I18N + /* If the node may accept `multi byte'. */ + if (dfa->nodes[cur_node].accept_mb) + { + naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input, + str_idx); + if (naccepted > 1) + { + re_dfastate_t *dest_state; + int next_node = dfa->nexts[cur_node]; + int next_idx = str_idx + naccepted; + dest_state = mctx->state_log[next_idx]; + re_node_set_empty (&union_set); + if (dest_state) + { + err = re_node_set_merge (&union_set, &dest_state->nodes); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&union_set); + return err; + } + } + result = re_node_set_insert (&union_set, next_node); + if (BE (result < 0, 0)) + { + re_node_set_free (&union_set); + return REG_ESPACE; + } + mctx->state_log[next_idx] = re_acquire_state (&err, dfa, + &union_set); + if (BE (mctx->state_log[next_idx] == NULL + && err != REG_NOERROR, 0)) + { + re_node_set_free (&union_set); + return err; + } + } + } +#endif /* RE_ENABLE_I18N */ + if (naccepted + || check_node_accept (mctx, dfa->nodes + cur_node, str_idx)) + { + result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]); + if (BE (result < 0, 0)) + { + re_node_set_free (&union_set); + return REG_ESPACE; + } + } + } + re_node_set_free (&union_set); + return REG_NOERROR; +} + +/* For all the nodes in CUR_NODES, add the epsilon closures of them to + CUR_NODES, however exclude the nodes which are: + - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN. + - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN. +*/ + +static reg_errcode_t +internal_function +check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes, + int ex_subexp, int type) +{ + reg_errcode_t err; + int idx, outside_node; + re_node_set new_nodes; +#ifdef DEBUG + assert (cur_nodes->nelem); +#endif + err = re_node_set_alloc (&new_nodes, cur_nodes->nelem); + if (BE (err != REG_NOERROR, 0)) + return err; + /* Create a new node set NEW_NODES with the nodes which are epsilon + closures of the node in CUR_NODES. */ + + for (idx = 0; idx < cur_nodes->nelem; ++idx) + { + int cur_node = cur_nodes->elems[idx]; + const re_node_set *eclosure = dfa->eclosures + cur_node; + outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type); + if (outside_node == -1) + { + /* There are no problematic nodes, just merge them. */ + err = re_node_set_merge (&new_nodes, eclosure); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&new_nodes); + return err; + } + } + else + { + /* There are problematic nodes, re-calculate incrementally. */ + err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node, + ex_subexp, type); + if (BE (err != REG_NOERROR, 0)) + { + re_node_set_free (&new_nodes); + return err; + } + } + } + re_node_set_free (cur_nodes); + *cur_nodes = new_nodes; + return REG_NOERROR; +} + +/* Helper function for check_arrival_expand_ecl. + Check incrementally the epsilon closure of TARGET, and if it isn't + problematic append it to DST_NODES. */ + +static reg_errcode_t +internal_function +check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes, + int target, int ex_subexp, int type) +{ + int cur_node; + for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);) + { + int err; + + if (dfa->nodes[cur_node].type == type + && dfa->nodes[cur_node].opr.idx == ex_subexp) + { + if (type == OP_CLOSE_SUBEXP) + { + err = re_node_set_insert (dst_nodes, cur_node); + if (BE (err == -1, 0)) + return REG_ESPACE; + } + break; + } + err = re_node_set_insert (dst_nodes, cur_node); + if (BE (err == -1, 0)) + return REG_ESPACE; + if (dfa->edests[cur_node].nelem == 0) + break; + if (dfa->edests[cur_node].nelem == 2) + { + err = check_arrival_expand_ecl_sub (dfa, dst_nodes, + dfa->edests[cur_node].elems[1], + ex_subexp, type); + if (BE (err != REG_NOERROR, 0)) + return err; + } + cur_node = dfa->edests[cur_node].elems[0]; + } + return REG_NOERROR; +} + + +/* For all the back references in the current state, calculate the + destination of the back references by the appropriate entry + in MCTX->BKREF_ENTS. */ + +static reg_errcode_t +internal_function +expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes, + int cur_str, int subexp_num, int type) +{ + const re_dfa_t *const dfa = mctx->dfa; + reg_errcode_t err; + int cache_idx_start = search_cur_bkref_entry (mctx, cur_str); + struct re_backref_cache_entry *ent; + + if (cache_idx_start == -1) + return REG_NOERROR; + + restart: + ent = mctx->bkref_ents + cache_idx_start; + do + { + int to_idx, next_node; + + /* Is this entry ENT is appropriate? */ + if (!re_node_set_contains (cur_nodes, ent->node)) + continue; /* No. */ + + to_idx = cur_str + ent->subexp_to - ent->subexp_from; + /* Calculate the destination of the back reference, and append it + to MCTX->STATE_LOG. */ + if (to_idx == cur_str) + { + /* The backreference did epsilon transit, we must re-check all the + node in the current state. */ + re_node_set new_dests; + reg_errcode_t err2, err3; + next_node = dfa->edests[ent->node].elems[0]; + if (re_node_set_contains (cur_nodes, next_node)) + continue; + err = re_node_set_init_1 (&new_dests, next_node); + err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type); + err3 = re_node_set_merge (cur_nodes, &new_dests); + re_node_set_free (&new_dests); + if (BE (err != REG_NOERROR || err2 != REG_NOERROR + || err3 != REG_NOERROR, 0)) + { + err = (err != REG_NOERROR ? err + : (err2 != REG_NOERROR ? err2 : err3)); + return err; + } + /* TODO: It is still inefficient... */ + goto restart; + } + else + { + re_node_set union_set; + next_node = dfa->nexts[ent->node]; + if (mctx->state_log[to_idx]) + { + int ret; + if (re_node_set_contains (&mctx->state_log[to_idx]->nodes, + next_node)) + continue; + err = re_node_set_init_copy (&union_set, + &mctx->state_log[to_idx]->nodes); + ret = re_node_set_insert (&union_set, next_node); + if (BE (err != REG_NOERROR || ret < 0, 0)) + { + re_node_set_free (&union_set); + err = err != REG_NOERROR ? err : REG_ESPACE; + return err; + } + } + else + { + err = re_node_set_init_1 (&union_set, next_node); + if (BE (err != REG_NOERROR, 0)) + return err; + } + mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set); + re_node_set_free (&union_set); + if (BE (mctx->state_log[to_idx] == NULL + && err != REG_NOERROR, 0)) + return err; + } + } + while (ent++->more); + return REG_NOERROR; +} + +/* Build transition table for the state. + Return 1 if succeeded, otherwise return NULL. */ + +static int +internal_function +build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) +{ + reg_errcode_t err; + int i, j, ch, need_word_trtable = 0; + bitset_word_t elem, mask; + bool dests_node_malloced = false; + bool dest_states_malloced = false; + int ndests; /* Number of the destination states from `state'. */ + re_dfastate_t **trtable; + re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl; + re_node_set follows, *dests_node; + bitset_t *dests_ch; + bitset_t acceptable; + + struct dests_alloc + { + re_node_set dests_node[SBC_MAX]; + bitset_t dests_ch[SBC_MAX]; + } *dests_alloc; + + /* We build DFA states which corresponds to the destination nodes + from `state'. `dests_node[i]' represents the nodes which i-th + destination state contains, and `dests_ch[i]' represents the + characters which i-th destination state accepts. */ +#ifdef HAVE_ALLOCA + if (__libc_use_alloca (sizeof (struct dests_alloc))) + dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc)); + else +#endif + { + dests_alloc = re_malloc (struct dests_alloc, 1); + if (BE (dests_alloc == NULL, 0)) + return 0; + dests_node_malloced = true; + } + dests_node = dests_alloc->dests_node; + dests_ch = dests_alloc->dests_ch; + + /* Initialize transiton table. */ + state->word_trtable = state->trtable = NULL; + + /* At first, group all nodes belonging to `state' into several + destinations. */ + ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch); + if (BE (ndests <= 0, 0)) + { + if (dests_node_malloced) + free (dests_alloc); + /* Return 0 in case of an error, 1 otherwise. */ + if (ndests == 0) + { + state->trtable = (re_dfastate_t **) + calloc (sizeof (re_dfastate_t *), SBC_MAX); + return 1; + } + return 0; + } + + err = re_node_set_alloc (&follows, ndests + 1); + if (BE (err != REG_NOERROR, 0)) + goto out_free; + + /* Avoid arithmetic overflow in size calculation. */ + if (BE ((((SIZE_MAX - (sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX) + / (3 * sizeof (re_dfastate_t *))) + < ndests), + 0)) + goto out_free; + +#ifdef HAVE_ALLOCA + if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX + + ndests * 3 * sizeof (re_dfastate_t *))) + dest_states = (re_dfastate_t **) + alloca (ndests * 3 * sizeof (re_dfastate_t *)); + else +#endif + { + dest_states = (re_dfastate_t **) + malloc (ndests * 3 * sizeof (re_dfastate_t *)); + if (BE (dest_states == NULL, 0)) + { +out_free: + if (dest_states_malloced) + free (dest_states); + re_node_set_free (&follows); + for (i = 0; i < ndests; ++i) + re_node_set_free (dests_node + i); + if (dests_node_malloced) + free (dests_alloc); + return 0; + } + dest_states_malloced = true; + } + dest_states_word = dest_states + ndests; + dest_states_nl = dest_states_word + ndests; + bitset_empty (acceptable); + + /* Then build the states for all destinations. */ + for (i = 0; i < ndests; ++i) + { + int next_node; + re_node_set_empty (&follows); + /* Merge the follows of this destination states. */ + for (j = 0; j < dests_node[i].nelem; ++j) + { + next_node = dfa->nexts[dests_node[i].elems[j]]; + if (next_node != -1) + { + err = re_node_set_merge (&follows, dfa->eclosures + next_node); + if (BE (err != REG_NOERROR, 0)) + goto out_free; + } + } + dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0); + if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + /* If the new state has context constraint, + build appropriate states for these contexts. */ + if (dest_states[i]->has_constraint) + { + dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows, + CONTEXT_WORD); + if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + + if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1) + need_word_trtable = 1; + + dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows, + CONTEXT_NEWLINE); + if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0)) + goto out_free; + } + else + { + dest_states_word[i] = dest_states[i]; + dest_states_nl[i] = dest_states[i]; + } + bitset_merge (acceptable, dests_ch[i]); + } + + if (!BE (need_word_trtable, 0)) + { + /* We don't care about whether the following character is a word + character, or we are in a single-byte character set so we can + discern by looking at the character code: allocate a + 256-entry transition table. */ + trtable = state->trtable = + (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); + if (BE (trtable == NULL, 0)) + goto out_free; + + /* For all characters ch...: */ + for (i = 0; i < BITSET_WORDS; ++i) + for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; + elem; + mask <<= 1, elem >>= 1, ++ch) + if (BE (elem & 1, 0)) + { + /* There must be exactly one destination which accepts + character ch. See group_nodes_into_DFAstates. */ + for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) + ; + + /* j-th destination accepts the word character ch. */ + if (dfa->word_char[i] & mask) + trtable[ch] = dest_states_word[j]; + else + trtable[ch] = dest_states[j]; + } + } + else + { + /* We care about whether the following character is a word + character, and we are in a multi-byte character set: discern + by looking at the character code: build two 256-entry + transition tables, one starting at trtable[0] and one + starting at trtable[SBC_MAX]. */ + trtable = state->word_trtable = + (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX); + if (BE (trtable == NULL, 0)) + goto out_free; + + /* For all characters ch...: */ + for (i = 0; i < BITSET_WORDS; ++i) + for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1; + elem; + mask <<= 1, elem >>= 1, ++ch) + if (BE (elem & 1, 0)) + { + /* There must be exactly one destination which accepts + character ch. See group_nodes_into_DFAstates. */ + for (j = 0; (dests_ch[j][i] & mask) == 0; ++j) + ; + + /* j-th destination accepts the word character ch. */ + trtable[ch] = dest_states[j]; + trtable[ch + SBC_MAX] = dest_states_word[j]; + } + } + + /* new line */ + if (bitset_contain (acceptable, NEWLINE_CHAR)) + { + /* The current state accepts newline character. */ + for (j = 0; j < ndests; ++j) + if (bitset_contain (dests_ch[j], NEWLINE_CHAR)) + { + /* k-th destination accepts newline character. */ + trtable[NEWLINE_CHAR] = dest_states_nl[j]; + if (need_word_trtable) + trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j]; + /* There must be only one destination which accepts + newline. See group_nodes_into_DFAstates. */ + break; + } + } + + if (dest_states_malloced) + free (dest_states); + + re_node_set_free (&follows); + for (i = 0; i < ndests; ++i) + re_node_set_free (dests_node + i); + + if (dests_node_malloced) + free (dests_alloc); + + return 1; +} + +/* Group all nodes belonging to STATE into several destinations. + Then for all destinations, set the nodes belonging to the destination + to DESTS_NODE[i] and set the characters accepted by the destination + to DEST_CH[i]. This function return the number of destinations. */ + +static int +internal_function +group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state, + re_node_set *dests_node, bitset_t *dests_ch) +{ + reg_errcode_t err; + int result; + int i, j, k; + int ndests; /* Number of the destinations from `state'. */ + bitset_t accepts; /* Characters a node can accept. */ + const re_node_set *cur_nodes = &state->nodes; + bitset_empty (accepts); + ndests = 0; + + /* For all the nodes belonging to `state', */ + for (i = 0; i < cur_nodes->nelem; ++i) + { + re_token_t *node = &dfa->nodes[cur_nodes->elems[i]]; + re_token_type_t type = node->type; + unsigned int constraint = node->constraint; + + /* Enumerate all single byte character this node can accept. */ + if (type == CHARACTER) + bitset_set (accepts, node->opr.c); + else if (type == SIMPLE_BRACKET) + { + bitset_merge (accepts, node->opr.sbcset); + } + else if (type == OP_PERIOD) + { +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + bitset_merge (accepts, dfa->sb_char); + else +#endif + bitset_set_all (accepts); + if (!(dfa->syntax & RE_DOT_NEWLINE)) + bitset_clear (accepts, '\n'); + if (dfa->syntax & RE_DOT_NOT_NULL) + bitset_clear (accepts, '\0'); + } +#ifdef RE_ENABLE_I18N + else if (type == OP_UTF8_PERIOD) + { + memset (accepts, '\xff', sizeof (bitset_t) / 2); + if (!(dfa->syntax & RE_DOT_NEWLINE)) + bitset_clear (accepts, '\n'); + if (dfa->syntax & RE_DOT_NOT_NULL) + bitset_clear (accepts, '\0'); + } +#endif + else + continue; + + /* Check the `accepts' and sift the characters which are not + match it the context. */ + if (constraint) + { + if (constraint & NEXT_NEWLINE_CONSTRAINT) + { + bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR); + bitset_empty (accepts); + if (accepts_newline) + bitset_set (accepts, NEWLINE_CHAR); + else + continue; + } + if (constraint & NEXT_ENDBUF_CONSTRAINT) + { + bitset_empty (accepts); + continue; + } + + if (constraint & NEXT_WORD_CONSTRAINT) + { + bitset_word_t any_set = 0; + if (type == CHARACTER && !node->word_char) + { + bitset_empty (accepts); + continue; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + for (j = 0; j < BITSET_WORDS; ++j) + any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j])); + else +#endif + for (j = 0; j < BITSET_WORDS; ++j) + any_set |= (accepts[j] &= dfa->word_char[j]); + if (!any_set) + continue; + } + if (constraint & NEXT_NOTWORD_CONSTRAINT) + { + bitset_word_t any_set = 0; + if (type == CHARACTER && node->word_char) + { + bitset_empty (accepts); + continue; + } +#ifdef RE_ENABLE_I18N + if (dfa->mb_cur_max > 1) + for (j = 0; j < BITSET_WORDS; ++j) + any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j])); + else +#endif + for (j = 0; j < BITSET_WORDS; ++j) + any_set |= (accepts[j] &= ~dfa->word_char[j]); + if (!any_set) + continue; + } + } + + /* Then divide `accepts' into DFA states, or create a new + state. Above, we make sure that accepts is not empty. */ + for (j = 0; j < ndests; ++j) + { + bitset_t intersec; /* Intersection sets, see below. */ + bitset_t remains; + /* Flags, see below. */ + bitset_word_t has_intersec, not_subset, not_consumed; + + /* Optimization, skip if this state doesn't accept the character. */ + if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c)) + continue; + + /* Enumerate the intersection set of this state and `accepts'. */ + has_intersec = 0; + for (k = 0; k < BITSET_WORDS; ++k) + has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k]; + /* And skip if the intersection set is empty. */ + if (!has_intersec) + continue; + + /* Then check if this state is a subset of `accepts'. */ + not_subset = not_consumed = 0; + for (k = 0; k < BITSET_WORDS; ++k) + { + not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k]; + not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k]; + } + + /* If this state isn't a subset of `accepts', create a + new group state, which has the `remains'. */ + if (not_subset) + { + bitset_copy (dests_ch[ndests], remains); + bitset_copy (dests_ch[j], intersec); + err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]); + if (BE (err != REG_NOERROR, 0)) + goto error_return; + ++ndests; + } + + /* Put the position in the current group. */ + result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]); + if (BE (result < 0, 0)) + goto error_return; + + /* If all characters are consumed, go to next node. */ + if (!not_consumed) + break; + } + /* Some characters remain, create a new group. */ + if (j == ndests) + { + bitset_copy (dests_ch[ndests], accepts); + err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]); + if (BE (err != REG_NOERROR, 0)) + goto error_return; + ++ndests; + bitset_empty (accepts); + } + } + return ndests; + error_return: + for (j = 0; j < ndests; ++j) + re_node_set_free (dests_node + j); + return -1; +} + +#ifdef RE_ENABLE_I18N +/* Check how many bytes the node `dfa->nodes[node_idx]' accepts. + Return the number of the bytes the node accepts. + STR_IDX is the current index of the input string. + + This function handles the nodes which can accept one character, or + one collating element like '.', '[a-z]', opposite to the other nodes + can only accept one byte. */ + +static int +internal_function +check_node_accept_bytes (const re_dfa_t *dfa, int node_idx, + const re_string_t *input, int str_idx) +{ + const re_token_t *node = dfa->nodes + node_idx; + int char_len, elem_len; + int i; + wint_t wc; + + if (BE (node->type == OP_UTF8_PERIOD, 0)) + { + unsigned char c = re_string_byte_at (input, str_idx), d; + if (BE (c < 0xc2, 1)) + return 0; + + if (str_idx + 2 > input->len) + return 0; + + d = re_string_byte_at (input, str_idx + 1); + if (c < 0xe0) + return (d < 0x80 || d > 0xbf) ? 0 : 2; + else if (c < 0xf0) + { + char_len = 3; + if (c == 0xe0 && d < 0xa0) + return 0; + } + else if (c < 0xf8) + { + char_len = 4; + if (c == 0xf0 && d < 0x90) + return 0; + } + else if (c < 0xfc) + { + char_len = 5; + if (c == 0xf8 && d < 0x88) + return 0; + } + else if (c < 0xfe) + { + char_len = 6; + if (c == 0xfc && d < 0x84) + return 0; + } + else + return 0; + + if (str_idx + char_len > input->len) + return 0; + + for (i = 1; i < char_len; ++i) + { + d = re_string_byte_at (input, str_idx + i); + if (d < 0x80 || d > 0xbf) + return 0; + } + return char_len; + } + + char_len = re_string_char_size_at (input, str_idx); + if (node->type == OP_PERIOD) + { + if (char_len <= 1) + return 0; + /* FIXME: I don't think this if is needed, as both '\n' + and '\0' are char_len == 1. */ + /* '.' accepts any one character except the following two cases. */ + if ((!(dfa->syntax & RE_DOT_NEWLINE) && + re_string_byte_at (input, str_idx) == '\n') || + ((dfa->syntax & RE_DOT_NOT_NULL) && + re_string_byte_at (input, str_idx) == '\0')) + return 0; + return char_len; + } + + elem_len = re_string_elem_size_at (input, str_idx); + wc = __btowc(*(input->mbs+str_idx)); + if (((elem_len <= 1 && char_len <= 1) || char_len == 0) && (wc != WEOF && wc < SBC_MAX)) + return 0; + + if (node->type == COMPLEX_BRACKET) + { + const re_charset_t *cset = node->opr.mbcset; +# ifdef _LIBC + const unsigned char *pin + = ((const unsigned char *) re_string_get_buffer (input) + str_idx); + int j; + uint32_t nrules; +# endif /* _LIBC */ + int match_len = 0; + wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars) + ? re_string_wchar_at (input, str_idx) : 0); + + /* match with multibyte character? */ + for (i = 0; i < cset->nmbchars; ++i) + if (wc == cset->mbchars[i]) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + /* match with character_class? */ + for (i = 0; i < cset->nchar_classes; ++i) + { + wctype_t wt = cset->char_classes[i]; + if (__iswctype (wc, wt)) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + } + +# ifdef _LIBC + nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules != 0) + { + unsigned int in_collseq = 0; + const int32_t *table, *indirect; + const unsigned char *weights, *extra; + const char *collseqwc; + /* This #include defines a local function! */ +# include + + /* match with collating_symbol? */ + if (cset->ncoll_syms) + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); + for (i = 0; i < cset->ncoll_syms; ++i) + { + const unsigned char *coll_sym = extra + cset->coll_syms[i]; + /* Compare the length of input collating element and + the length of current collating element. */ + if (*coll_sym != elem_len) + continue; + /* Compare each bytes. */ + for (j = 0; j < *coll_sym; j++) + if (pin[j] != coll_sym[1 + j]) + break; + if (j == *coll_sym) + { + /* Match if every bytes is equal. */ + match_len = j; + goto check_node_accept_bytes_match; + } + } + + if (cset->nranges) + { + if (elem_len <= char_len) + { + collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC); + in_collseq = __collseq_table_lookup (collseqwc, wc); + } + else + in_collseq = find_collation_sequence_value (pin, elem_len); + } + /* match with range expression? */ + for (i = 0; i < cset->nranges; ++i) + if (cset->range_starts[i] <= in_collseq + && in_collseq <= cset->range_ends[i]) + { + match_len = elem_len; + goto check_node_accept_bytes_match; + } + + /* match with equivalence_class? */ + if (cset->nequiv_classes) + { + const unsigned char *cp = pin; + table = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); + weights = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); + extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); + indirect = (const int32_t *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); + int32_t idx = findidx (&cp); + if (idx > 0) + for (i = 0; i < cset->nequiv_classes; ++i) + { + int32_t equiv_class_idx = cset->equiv_classes[i]; + size_t weight_len = weights[idx & 0xffffff]; + if (weight_len == weights[equiv_class_idx & 0xffffff] + && (idx >> 24) == (equiv_class_idx >> 24)) + { + int cnt = 0; + + idx &= 0xffffff; + equiv_class_idx &= 0xffffff; + + while (cnt <= weight_len + && (weights[equiv_class_idx + 1 + cnt] + == weights[idx + 1 + cnt])) + ++cnt; + if (cnt > weight_len) + { + match_len = elem_len; + goto check_node_accept_bytes_match; + } + } + } + } + } + else +# endif /* _LIBC */ + { + /* match with range expression? */ +#if __GNUC__ >= 2 + wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'}; +#else + wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'}; + cmp_buf[2] = wc; +#endif + for (i = 0; i < cset->nranges; ++i) + { + cmp_buf[0] = cset->range_starts[i]; + cmp_buf[4] = cset->range_ends[i]; + if (wcscoll (cmp_buf, cmp_buf + 2) <= 0 + && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0) + { + match_len = char_len; + goto check_node_accept_bytes_match; + } + } + } + check_node_accept_bytes_match: + if (!cset->non_match) + return match_len; + else + { + if (match_len > 0) + return 0; + else + return (elem_len > char_len) ? elem_len : char_len; + } + } + return 0; +} + +# ifdef _LIBC +static unsigned int +internal_function +find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len) +{ + uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); + if (nrules == 0) + { + if (mbs_len == 1) + { + /* No valid character. Match it as a single byte character. */ + const unsigned char *collseq = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); + return collseq[mbs[0]]; + } + return UINT_MAX; + } + else + { + int32_t idx; + const unsigned char *extra = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); + int32_t extrasize = (const unsigned char *) + _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra; + + for (idx = 0; idx < extrasize;) + { + int mbs_cnt, found = 0; + int32_t elem_mbs_len; + /* Skip the name of collating element name. */ + idx = idx + extra[idx] + 1; + elem_mbs_len = extra[idx++]; + if (mbs_len == elem_mbs_len) + { + for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt) + if (extra[idx + mbs_cnt] != mbs[mbs_cnt]) + break; + if (mbs_cnt == elem_mbs_len) + /* Found the entry. */ + found = 1; + } + /* Skip the byte sequence of the collating element. */ + idx += elem_mbs_len; + /* Adjust for the alignment. */ + idx = (idx + 3) & ~3; + /* Skip the collation sequence value. */ + idx += sizeof (uint32_t); + /* Skip the wide char sequence of the collating element. */ + idx = idx + sizeof (uint32_t) * (extra[idx] + 1); + /* If we found the entry, return the sequence value. */ + if (found) + return *(uint32_t *) (extra + idx); + /* Skip the collation sequence value. */ + idx += sizeof (uint32_t); + } + return UINT_MAX; + } +} +# endif /* _LIBC */ +#endif /* RE_ENABLE_I18N */ + +/* Check whether the node accepts the byte which is IDX-th + byte of the INPUT. */ + +static int +internal_function +check_node_accept (const re_match_context_t *mctx, const re_token_t *node, + int idx) +{ + unsigned char ch; + ch = re_string_byte_at (&mctx->input, idx); + switch (node->type) + { + case CHARACTER: + if (node->opr.c != ch) + return 0; + break; + + case SIMPLE_BRACKET: + if (!bitset_contain (node->opr.sbcset, ch)) + return 0; + break; + +#ifdef RE_ENABLE_I18N + case OP_UTF8_PERIOD: + if (ch >= 0x80) + return 0; + /* FALLTHROUGH */ +#endif + case OP_PERIOD: + if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE)) + || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL))) + return 0; + break; + + default: + return 0; + } + + if (node->constraint) + { + /* The node has constraints. Check whether the current context + satisfies the constraints. */ + unsigned int context = re_string_context_at (&mctx->input, idx, + mctx->eflags); + if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context)) + return 0; + } + + return 1; +} + +/* Extend the buffers, if the buffers have run out. */ + +static reg_errcode_t +internal_function +extend_buffers (re_match_context_t *mctx) +{ + reg_errcode_t ret; + re_string_t *pstr = &mctx->input; + + /* Avoid overflow. */ + if (BE (INT_MAX / 2 / sizeof (re_dfastate_t *) <= pstr->bufs_len, 0)) + return REG_ESPACE; + + /* Double the lengthes of the buffers. */ + ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2); + if (BE (ret != REG_NOERROR, 0)) + return ret; + + if (mctx->state_log != NULL) + { + /* And double the length of state_log. */ + /* XXX We have no indication of the size of this buffer. If this + allocation fail we have no indication that the state_log array + does not have the right size. */ + re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *, + pstr->bufs_len + 1); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + mctx->state_log = new_array; + } + + /* Then reconstruct the buffers. */ + if (pstr->icase) + { +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + { + ret = build_wcs_upper_buffer (pstr); + if (BE (ret != REG_NOERROR, 0)) + return ret; + } + else +#endif /* RE_ENABLE_I18N */ + build_upper_buffer (pstr); + } + else + { +#ifdef RE_ENABLE_I18N + if (pstr->mb_cur_max > 1) + build_wcs_buffer (pstr); + else +#endif /* RE_ENABLE_I18N */ + { + if (pstr->trans != NULL) + re_string_translate_buffer (pstr); + } + } + return REG_NOERROR; +} + + +/* Functions for matching context. */ + +/* Initialize MCTX. */ + +static reg_errcode_t +internal_function +match_ctx_init (re_match_context_t *mctx, int eflags, int n) +{ + mctx->eflags = eflags; + mctx->match_last = -1; + if (n > 0) + { + mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n); + mctx->sub_tops = re_malloc (re_sub_match_top_t *, n); + if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0)) + return REG_ESPACE; + } + /* Already zero-ed by the caller. + else + mctx->bkref_ents = NULL; + mctx->nbkref_ents = 0; + mctx->nsub_tops = 0; */ + mctx->abkref_ents = n; + mctx->max_mb_elem_len = 1; + mctx->asub_tops = n; + return REG_NOERROR; +} + +/* Clean the entries which depend on the current input in MCTX. + This function must be invoked when the matcher changes the start index + of the input, or changes the input string. */ + +static void +internal_function +match_ctx_clean (re_match_context_t *mctx) +{ + int st_idx; + for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx) + { + int sl_idx; + re_sub_match_top_t *top = mctx->sub_tops[st_idx]; + for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx) + { + re_sub_match_last_t *last = top->lasts[sl_idx]; + re_free (last->path.array); + re_free (last); + } + re_free (top->lasts); + if (top->path) + { + re_free (top->path->array); + re_free (top->path); + } + free (top); + } + + mctx->nsub_tops = 0; + mctx->nbkref_ents = 0; +} + +/* Free all the memory associated with MCTX. */ + +static void +internal_function +match_ctx_free (re_match_context_t *mctx) +{ + /* First, free all the memory associated with MCTX->SUB_TOPS. */ + match_ctx_clean (mctx); + re_free (mctx->sub_tops); + re_free (mctx->bkref_ents); +} + +/* Add a new backreference entry to MCTX. + Note that we assume that caller never call this function with duplicate + entry, and call with STR_IDX which isn't smaller than any existing entry. +*/ + +static reg_errcode_t +internal_function +match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from, + int to) +{ + if (mctx->nbkref_ents >= mctx->abkref_ents) + { + struct re_backref_cache_entry* new_entry; + new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry, + mctx->abkref_ents * 2); + if (BE (new_entry == NULL, 0)) + { + re_free (mctx->bkref_ents); + return REG_ESPACE; + } + mctx->bkref_ents = new_entry; + memset (mctx->bkref_ents + mctx->nbkref_ents, '\0', + sizeof (struct re_backref_cache_entry) * mctx->abkref_ents); + mctx->abkref_ents *= 2; + } + if (mctx->nbkref_ents > 0 + && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx) + mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1; + + mctx->bkref_ents[mctx->nbkref_ents].node = node; + mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx; + mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from; + mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to; + + /* This is a cache that saves negative results of check_dst_limits_calc_pos. + If bit N is clear, means that this entry won't epsilon-transition to + an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If + it is set, check_dst_limits_calc_pos_1 will recurse and try to find one + such node. + + A backreference does not epsilon-transition unless it is empty, so set + to all zeros if FROM != TO. */ + mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map + = (from == to ? ~0 : 0); + + mctx->bkref_ents[mctx->nbkref_ents++].more = 0; + if (mctx->max_mb_elem_len < to - from) + mctx->max_mb_elem_len = to - from; + return REG_NOERROR; +} + +/* Search for the first entry which has the same str_idx, or -1 if none is + found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */ + +static int +internal_function +search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx) +{ + int left, right, mid, last; + last = right = mctx->nbkref_ents; + for (left = 0; left < right;) + { + mid = (left + right) / 2; + if (mctx->bkref_ents[mid].str_idx < str_idx) + left = mid + 1; + else + right = mid; + } + if (left < last && mctx->bkref_ents[left].str_idx == str_idx) + return left; + else + return -1; +} + +/* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches + at STR_IDX. */ + +static reg_errcode_t +internal_function +match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx) +{ +#ifdef DEBUG + assert (mctx->sub_tops != NULL); + assert (mctx->asub_tops > 0); +#endif + if (BE (mctx->nsub_tops == mctx->asub_tops, 0)) + { + int new_asub_tops = mctx->asub_tops * 2; + re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops, + re_sub_match_top_t *, + new_asub_tops); + if (BE (new_array == NULL, 0)) + return REG_ESPACE; + mctx->sub_tops = new_array; + mctx->asub_tops = new_asub_tops; + } + mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t)); + if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0)) + return REG_ESPACE; + mctx->sub_tops[mctx->nsub_tops]->node = node; + mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx; + return REG_NOERROR; +} + +/* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches + at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */ + +static re_sub_match_last_t * +internal_function +match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx) +{ + re_sub_match_last_t *new_entry; + if (BE (subtop->nlasts == subtop->alasts, 0)) + { + int new_alasts = 2 * subtop->alasts + 1; + re_sub_match_last_t **new_array = re_realloc (subtop->lasts, + re_sub_match_last_t *, + new_alasts); + if (BE (new_array == NULL, 0)) + return NULL; + subtop->lasts = new_array; + subtop->alasts = new_alasts; + } + new_entry = calloc (1, sizeof (re_sub_match_last_t)); + if (BE (new_entry != NULL, 1)) + { + subtop->lasts[subtop->nlasts] = new_entry; + new_entry->node = node; + new_entry->str_idx = str_idx; + ++subtop->nlasts; + } + return new_entry; +} + +static void +internal_function +sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts, + re_dfastate_t **limited_sts, int last_node, int last_str_idx) +{ + sctx->sifted_states = sifted_sts; + sctx->limited_states = limited_sts; + sctx->last_node = last_node; + sctx->last_str_idx = last_str_idx; + re_node_set_init_empty (&sctx->limits); +} diff --git a/deps/zlib/crc32.c b/deps/zlib/crc32.c new file mode 100644 index 000000000..91be372d2 --- /dev/null +++ b/deps/zlib/crc32.c @@ -0,0 +1,442 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2006, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) ((((w)>>24)&0xff)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); +local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2); + + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + uInt len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +local uLong crc32_combine_(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case (also disallow negative lengths) */ + if (len2 <= 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} + +uLong ZEXPORT crc32_combine64(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} diff --git a/deps/zlib/crc32.h b/deps/zlib/crc32.h new file mode 100644 index 000000000..8053b6117 --- /dev/null +++ b/deps/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/deps/zlib/zconf.h b/deps/zlib/zconf.h index 494992aba..150814361 100644 --- a/deps/zlib/zconf.h +++ b/deps/zlib/zconf.h @@ -10,9 +10,6 @@ #include "../../src/common.h" -#define NO_GZIP -#define STDC - /* Jeez, don't complain about non-prototype * forms, we didn't write zlib */ #if defined(_MSC_VER) diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 000000000..655afeba8 --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,111 @@ +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. + +When a function fails, an error is set on the error variable **and** returns one of the generic error codes. + +~~~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; + } + + ... + + if (try_to_parse(path, error) < 0) + return GIT_ERROR; + + ... +} +~~~ + +The simple 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: + + - `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 + +- `void giterr_free(git_error *)`: takes an error and frees it. This function is available in the external API. + +- `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 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). + +The new error code return values +-------------------------------- + +We are doing this the POSIX way: one error code for each "expected failure", and a generic error code for all the critical failures. + +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**. + +Please be smart when returning error codes. Functions have max two "expected errors", and in most cases only one. + +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 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. + + 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! + +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. + + 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. + +- The `git_error **` argument is always the last in the signature of all API calls. No exceptions. + +- 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!!** + + ~~~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) + { + 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 */ + } + } + +- 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. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..4c34e4ab5 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,2 @@ +general +showindex diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 000000000..fe99c75cb --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,15 @@ +.PHONY: all + +CC = gcc +CFLAGS = -g -I../include -I../src +LFLAGS = -L../build -lgit2 -lz +APPS = general showindex diff + +all: $(APPS) + +% : %.c + $(CC) -o $@ $(CFLAGS) $< $(LFLAGS) + +clean: + $(RM) $(APPS) + $(RM) -r *.dSYM diff --git a/examples/diff.c b/examples/diff.c new file mode 100644 index 000000000..1b4ab549b --- /dev/null +++ b/examples/diff.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +void check(int error, const char *message) +{ + if (error) { + fprintf(stderr, "%s (%d)\n", message, error); + exit(1); + } +} + +int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree) +{ + int err = 0; + size_t len = strlen(identifier); + git_oid oid; + git_object *obj = NULL; + + /* try to resolve as OID */ + if (git_oid_fromstrn(&oid, identifier, len) == 0) + git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY); + + /* try to resolve as reference */ + if (obj == NULL) { + git_reference *ref, *resolved; + if (git_reference_lookup(&ref, repo, identifier) == 0) { + git_reference_resolve(&resolved, ref); + git_reference_free(ref); + if (resolved) { + git_object_lookup(&obj, repo, git_reference_oid(resolved), GIT_OBJ_ANY); + git_reference_free(resolved); + } + } + } + + if (obj == NULL) + return GIT_ENOTFOUND; + + 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[] = { + "\033[m", /* reset */ + "\033[1m", /* bold */ + "\033[31m", /* red */ + "\033[32m", /* green */ + "\033[36m" /* cyan */ +}; + +int printer( + void *data, + git_diff_delta *delta, + git_diff_range *range, + char usage, + const char *line, + size_t line_len) +{ + int *last_color = data, color = 0; + + if (*last_color >= 0) { + switch (usage) { + case GIT_DIFF_LINE_ADDITION: color = 3; break; + case GIT_DIFF_LINE_DELETION: color = 2; break; + case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; + case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; + case GIT_DIFF_LINE_FILE_HDR: color = 1; break; + case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; + default: color = 0; + } + if (color != *last_color) { + if (*last_color == 1 || color == 1) + fputs(colors[0], stdout); + fputs(colors[color], stdout); + *last_color = color; + } + } + + fputs(line, stdout); + return 0; +} + +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; + strval = strtoul(arg + len, &endptr, 0); + if (endptr == arg) + return 0; + *val = strval; + return 1; +} + +int check_str_param(const char *arg, const char *pattern, char **val) +{ + size_t len = strlen(pattern); + if (strncmp(arg, pattern, len)) + return 0; + *val = (char *)(arg + len); + return 1; +} + +void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: diff [ []]\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + char path[GIT_PATH_MAX]; + git_repository *repo = NULL; + git_tree *t1 = NULL, *t2 = NULL; + git_diff_options opts = {0}; + git_diff_list *diff; + int i, color = -1, compact = 0, cached = 0; + char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL; + + /* parse arguments as copied from git-diff */ + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + if (treeish1 == NULL) + treeish1 = a; + else if (treeish2 == NULL) + treeish2 = a; + else + usage("Only one or two tree identifiers can be provided", NULL); + } + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || + !strcmp(a, "--patch")) + compact = 0; + else if (!strcmp(a, "--cached")) + cached = 1; + else if (!strcmp(a, "--name-status")) + compact = 1; + else if (!strcmp(a, "--color")) + color = 0; + else if (!strcmp(a, "--no-color")) + color = -1; + else if (!strcmp(a, "-R")) + opts.flags |= GIT_DIFF_REVERSE; + else if (!strcmp(a, "-a") || !strcmp(a, "--text")) + opts.flags |= GIT_DIFF_FORCE_TEXT; + else if (!strcmp(a, "--ignore-space-at-eol")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + else if (!strcmp(a, "--ignored")) + opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + else if (!strcmp(a, "--untracked")) + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + else if (!check_uint16_param(a, "-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)) + usage("Unknown arg", a); + } + + /* open repo */ + + check(git_repository_discover(path, sizeof(path), dir, 0, "/"), + "Could not discover repository"); + check(git_repository_open(&repo, path), + "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(repo, &opts, t1, t2, &diff), "Diff"); + else if (t1 && cached) + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + else if (t1) { + git_diff_list *diff2; + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + check(git_diff_workdir_to_index(repo, &opts, &diff2), "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_index_to_tree(repo, &opts, t1, &diff), "Diff"); + } + else + check(git_diff_workdir_to_index(repo, &opts, &diff), "Diff"); + + if (color >= 0) + fputs(colors[0], stdout); + + if (compact) + check(git_diff_print_compact(diff, &color, printer), "Displaying diff"); + else + check(git_diff_print_patch(diff, &color, printer), "Displaying diff"); + + if (color >= 0) + fputs(colors[0], stdout); + + git_diff_list_free(diff); + git_tree_free(t1); + git_tree_free(t2); + git_repository_free(repo); + + return 0; +} + diff --git a/examples/general.c b/examples/general.c new file mode 100644 index 000000000..5269785f2 --- /dev/null +++ b/examples/general.c @@ -0,0 +1,451 @@ +// [**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 language which +// supports C bindings. +// +// This file is an example of using that API in a real, compilable C file. +// As the API is updated, this file will be updated to demonstrate the +// new functionality. +// +// If you're trying to write something in C using [libgit2][lg], you will also want +// to check out the generated [API documentation][ap] and the [Usage Guide][ug]. We've +// tried to link to the relevant sections of the API docs in each section in this file. +// +// **libgit2** only implements the core plumbing functions, not really the higher +// level porcelain stuff. For a primer on Git Internals that you will need to know +// to work with Git at this level, check out [Chapter 9][pg] of the Pro Git book. +// +// [lg]: http://libgit2.github.com +// [ap]: http://libgit2.github.com/libgit2 +// [ug]: http://libgit2.github.com/api.html +// [pg]: http://progit.org/book/ch9-0.html + +// ### Includes + +// Including the `git2.h` header will include all the other libgit2 headers that you need. +// It should be the only thing you need to include in order to compile properly and get +// all the libgit2 API. +#include +#include + +int main (int argc, char** argv) +{ + // ### Opening the Repository + + // There are a couple of methods for opening a repository, this being the simplest. + // There are also [methods][me] for specifying the index file and work tree locations, here + // we are assuming they are in the normal places. + // + // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + git_repository *repo; + if (argc > 1) { + git_repository_open(&repo, argv[1]); + } else { + git_repository_open(&repo, "/opt/libgit2-test/.git"); + } + + // ### SHA-1 Value Conversions + + // For our first example, we will convert a 40 character hex value to the 20 byte raw SHA1 value. + printf("*Hex to Raw*\n"); + char hex[] = "fd6e612585290339ea8bf39c692a7ff6a29cb7c3"; + + // The `git_oid` is the structure that keeps the SHA value. We will use this throughout the example + // for storing the value of the current SHA key we're working with. + git_oid oid; + git_oid_fromstr(&oid, hex); + + // Once we've converted the string into the oid value, we can get the raw value of the SHA. + printf("Raw 20 bytes: [%.20s]\n", (&oid)->id); + + // Next we will convert the 20 byte raw SHA1 value to a human readable 40 char hex value. + printf("\n*Raw to Hex*\n"); + char out[41]; + out[40] = '\0'; + + // If you have a oid, you can easily get the hex value of the SHA as well. + git_oid_fmt(out, &oid); + printf("SHA hex string: %s\n", out); + + // ### Working with the Object Database + // **libgit2** provides [direct access][odb] to the object database. + // The object database is where the actual objects are stored in Git. For + // working with raw objects, we'll need to get this structure from the + // repository. + // [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + git_odb *odb; + git_repository_odb(&odb, repo); + + // #### Raw Object Reading + + printf("\n*Raw Object Read*\n"); + git_odb_object *obj; + git_otype otype; + const unsigned char *data; + const char *str_type; + int error; + + // We can read raw objects directly from the object database if we have the oid (SHA) + // of the object. This allows us to access objects without knowing thier type and inspect + // the raw bytes unparsed. + error = git_odb_read(&obj, odb, &oid); + + // A raw object only has three properties - the type (commit, blob, tree or tag), the size + // of the raw data and the raw, unparsed data itself. For a commit or tag, that raw data + // is human readable plain ASCII text. For a blob it is just file contents, so it could be + // text or binary data. For a tree it is a special binary format, so it's unlikely to be + // hugely helpful as a raw object. + data = (const unsigned char *)git_odb_object_data(obj); + otype = git_odb_object_type(obj); + + // We provide methods to convert from the object type which is an enum, to a string + // representation of that value (and vice-versa). + str_type = git_object_type2string(otype); + printf("object length and type: %d, %s\n", + (int)git_odb_object_size(obj), + str_type); + + // For proper memory management, close the object when you are done with it or it will leak + // memory. + git_odb_object_free(obj); + + // #### Raw Object Writing + + printf("\n*Raw Object Write*\n"); + + // You can also write raw object data to Git. This is pretty cool because it gives you + // direct access to the key/value properties of Git. Here we'll write a new blob object + // that just contains a simple string. Notice that we have to specify the object type as + // the `git_otype` enum. + git_odb_write(&oid, odb, "test data", sizeof("test data") - 1, GIT_OBJ_BLOB); + + // Now that we've written the object, we can check out what SHA1 was generated when the + // object was written to our database. + git_oid_fmt(out, &oid); + printf("Written Object: %s\n", out); + + // ### Object Parsing + // libgit2 has methods to parse every object type in Git so you don't have to work directly + // with the raw data. This is much faster and simpler than trying to deal with the raw data + // yourself. + + // #### Commit Parsing + // [Parsing commit objects][pco] is simple and gives you access to all the data in the commit + // - the // author (name, email, datetime), committer (same), tree, message, encoding and parent(s). + // [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + + printf("\n*Commit Parsing*\n"); + + git_commit *commit; + git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1"); + + error = git_commit_lookup(&commit, repo, &oid); + + const git_signature *author, *cmtter; + const char *message; + time_t ctime; + unsigned int parents, p; + + // Each of the properties of the commit object are accessible via methods, including commonly + // needed variations, such as `git_commit_time` which returns the author time and `_message` + // which gives you the commit message. + message = git_commit_message(commit); + author = git_commit_author(commit); + cmtter = git_commit_committer(commit); + ctime = git_commit_time(commit); + + // The author and committer methods return [git_signature] structures, which give you name, email + // and `when`, which is a `git_time` structure, giving you a timestamp and timezone offset. + printf("Author: %s (%s)\n", author->name, author->email); + + // Commits can have zero or more parents. The first (root) commit will have no parents, most commits + // will have one, which is the commit it was based on, and merge commits will have two or more. + // Commits can technically have any number, though it's pretty rare to have more than two. + parents = git_commit_parentcount(commit); + for (p = 0;p < parents;p++) { + git_commit *parent; + git_commit_parent(&parent, commit, p); + git_oid_fmt(out, git_commit_id(parent)); + printf("Parent: %s\n", out); + git_commit_free(parent); + } + + // Don't forget to close the object to prevent memory leaks. You will have to do this for + // all the objects you open and parse. + git_commit_free(commit); + + // #### Writing Commits + // + // libgit2 provides a couple of methods to create commit objects easily as well. There are four + // different create signatures, we'll just show one of them here. You can read about the other + // ones in the [commit API docs][cd]. + // [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + + printf("\n*Commit Writing*\n"); + git_oid tree_id, parent_id, commit_id; + git_tree *tree; + git_commit *parent; + + // Creating signatures for an authoring identity and time is pretty simple - you will need to have + // this to create a commit in order to specify who created it and when. Default values for the name + // and email should be found in the `user.name` and `user.email` configuration options. See the `config` + // section of this example file to see how to access config values. + git_signature_new((git_signature **)&author, "Scott Chacon", "schacon@gmail.com", + 123456789, 60); + git_signature_new((git_signature **)&cmtter, "Scott A Chacon", "scott@github.com", + 987654321, 90); + + // Commit objects need a tree to point to and optionally one or more parents. Here we're creating oid + // objects to create the commit with, but you can also use + git_oid_fromstr(&tree_id, "28873d96b4e8f4e33ea30f4c682fd325f7ba56ac"); + git_tree_lookup(&tree, repo, &tree_id); + git_oid_fromstr(&parent_id, "f0877d0b841d75172ec404fc9370173dfffc20d1"); + git_commit_lookup(&parent, repo, &parent_id); + + // Here we actually create the commit object with a single call with all the values we need to create + // the commit. The SHA key is written to the `commit_id` variable here. + git_commit_create_v( + &commit_id, /* out id */ + repo, + NULL, /* do not update the HEAD */ + author, + cmtter, + NULL, /* use default message encoding */ + "example commit", + tree, + 1, parent); + + // Now we can take a look at the commit SHA we've generated. + git_oid_fmt(out, &commit_id); + printf("New Commit: %s\n", out); + + // #### Tag Parsing + // You can parse and create tags with the [tag management API][tm], which functions very similarly + // to the commit lookup, parsing and creation methods, since the objects themselves are very similar. + // [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + printf("\n*Tag Parsing*\n"); + git_tag *tag; + const char *tmessage, *tname; + git_otype ttype; + + // We create an oid for the tag object if we know the SHA and look it up in the repository the same + // way that we would a commit (or any other) object. + git_oid_fromstr(&oid, "bc422d45275aca289c51d79830b45cecebff7c3a"); + + error = git_tag_lookup(&tag, repo, &oid); + + // Now that we have the tag object, we can extract the information it generally contains: the target + // (usually a commit object), the type of the target object (usually 'commit'), the name ('v1.0'), + // the tagger (a git_signature - name, email, timestamp), and the tag message. + git_tag_target((git_object **)&commit, tag); + tname = git_tag_name(tag); // "test" + ttype = git_tag_type(tag); // GIT_OBJ_COMMIT (otype enum) + tmessage = git_tag_message(tag); // "tag message\n" + printf("Tag Message: %s\n", tmessage); + + git_commit_free(commit); + + // #### Tree Parsing + // [Tree parsing][tp] is a bit different than the other objects, in that we have a subtype which is the + // tree entry. This is not an actual object type in Git, but a useful structure for parsing and + // traversing tree entries. + // + // [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + printf("\n*Tree Parsing*\n"); + + const git_tree_entry *entry; + git_object *objt; + + // Create the oid and lookup the tree object just like the other objects. + git_oid_fromstr(&oid, "2a741c18ac5ff082a7caaec6e74db3075a1906b5"); + git_tree_lookup(&tree, repo, &oid); + + // Getting the count of entries in the tree so you can iterate over them if you want to. + int cnt = git_tree_entrycount(tree); // 3 + printf("tree entries: %d\n", cnt); + + entry = git_tree_entry_byindex(tree, 0); + printf("Entry name: %s\n", git_tree_entry_name(entry)); // "hello.c" + + // You can also access tree entries by name if you know the name of the entry you're looking for. + entry = git_tree_entry_byname(tree, "hello.c"); + git_tree_entry_name(entry); // "hello.c" + + // Once you have the entry object, you can access the content or subtree (or commit, in the case + // of submodules) that it points to. You can also get the mode if you want. + git_tree_entry_to_object(&objt, repo, entry); // blob + + // Remember to close the looked-up object once you are done using it + git_object_free(objt); + + // #### Blob Parsing + // + // The last object type is the simplest and requires the least parsing help. Blobs are just file + // contents and can contain anything, there is no structure to it. The main advantage to using the + // [simple blob api][ba] is that when you're creating blobs you don't have to calculate the size + // of the content. There is also a helper for reading a file from disk and writing it to the db and + // getting the oid back so you don't have to do all those steps yourself. + // + // [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + + printf("\n*Blob Parsing*\n"); + git_blob *blob; + + git_oid_fromstr(&oid, "af7574ea73f7b166f869ef1a39be126d9a186ae0"); + git_blob_lookup(&blob, repo, &oid); + + // You can access a buffer with the raw contents of the blob directly. + // Note that this buffer may not be contain ASCII data for certain blobs (e.g. binary files): + // do not consider the buffer a NULL-terminated string, and use the `git_blob_rawsize` attribute to + // find out its exact size in bytes + printf("Blob Size: %ld\n", git_blob_rawsize(blob)); // 8 + git_blob_rawcontent(blob); // "content" + + // ### Revwalking + // + // The libgit2 [revision walking api][rw] provides methods to traverse the directed graph created + // by the parent pointers of the commit objects. Since all commits point back to the commit that + // came directly before them, you can walk this parentage as a graph and find all the commits that + // were ancestors of (reachable from) a given starting point. This can allow you to create `git log` + // type functionality. + // + // [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + + printf("\n*Revwalking*\n"); + git_revwalk *walk; + git_commit *wcommit; + + git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1"); + + // To use the revwalker, create a new walker, tell it how you want to sort the output and then push + // one or more starting points onto the walker. If you want to emulate the output of `git log` you + // would push the SHA of the commit that HEAD points to into the walker and then start traversing them. + // You can also 'hide' commits that you want to stop at or not see any of their ancestors. So if you + // want to emulate `git log branch1..branch2`, you would push the oid of `branch2` and hide the oid + // of `branch1`. + git_revwalk_new(&walk, repo); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); + git_revwalk_push(walk, &oid); + + const git_signature *cauth; + const char *cmsg; + + // Now that we have the starting point pushed onto the walker, we can start asking for ancestors. It + // will return them in the sorting order we asked for as commit oids. + // We can then lookup and parse the commited pointed at by the returned OID; + // note that this operation is specially fast since the raw contents of the commit object will + // be cached in memory + while ((git_revwalk_next(&oid, walk)) == 0) { + error = git_commit_lookup(&wcommit, repo, &oid); + cmsg = git_commit_message(wcommit); + cauth = git_commit_author(wcommit); + printf("%s (%s)\n", cmsg, cauth->email); + git_commit_free(wcommit); + } + + // Like the other objects, be sure to free the revwalker when you're done to prevent memory leaks. + // Also, make sure that the repository being walked it not deallocated while the walk is in + // progress, or it will result in undefined behavior + git_revwalk_free(walk); + + // ### Index File Manipulation + // + // The [index file API][gi] allows you to read, traverse, update and write the Git index file + // (sometimes thought of as the staging area). + // + // [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + + printf("\n*Index Walking*\n"); + + git_index *index; + unsigned int i, ecount; + + // You can either open the index from the standard location in an open repository, as we're doing + // here, or you can open and manipulate any index file with `git_index_open_bare()`. The index + // for the repository will be located and loaded from disk. + git_repository_index(&index, repo); + + // For each entry in the index, you can get a bunch of information including the SHA (oid), path + // and mode which map to the tree objects that are written out. It also has filesystem properties + // to help determine what to inspect for changes (ctime, mtime, dev, ino, uid, gid, file_size and flags) + // All these properties are exported publicly in the `git_index_entry` struct + ecount = git_index_entrycount(index); + for (i = 0; i < ecount; ++i) { + git_index_entry *e = git_index_get(index, i); + + printf("path: %s\n", e->path); + printf("mtime: %d\n", (int)e->mtime.seconds); + printf("fs: %d\n", (int)e->file_size); + } + + git_index_free(index); + + // ### References + // + // The [reference API][ref] allows you to list, resolve, create and update references such as + // branches, tags and remote references (everything in the .git/refs directory). + // + // [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + + printf("\n*Reference Listing*\n"); + + // Here we will implement something like `git for-each-ref` simply listing out all available + // references and the object SHA they resolve to. + git_strarray ref_list; + git_reference_list(&ref_list, repo, GIT_REF_LISTALL); + + const char *refname; + git_reference *ref; + + // Now that we have the list of reference names, we can lookup each ref one at a time and + // resolve them to the SHA, then print both values out. + for (i = 0; i < ref_list.count; ++i) { + refname = ref_list.strings[i]; + git_reference_lookup(&ref, repo, refname); + + switch (git_reference_type(ref)) { + case GIT_REF_OID: + git_oid_fmt(out, git_reference_oid(ref)); + printf("%s [%s]\n", refname, out); + break; + + case GIT_REF_SYMBOLIC: + printf("%s => %s\n", refname, git_reference_target(ref)); + break; + default: + fprintf(stderr, "Unexpected reference type\n"); + exit(1); + } + } + + git_strarray_free(&ref_list); + + // ### Config Files + // + // The [config API][config] allows you to list and updatee config values in + // any of the accessible config file locations (system, global, local). + // + // [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + + printf("\n*Config Listing*\n"); + + const char *email; + int32_t j; + + git_config *cfg; + + // Open a config object so we can read global values from it. + git_config_open_ondisk(&cfg, "~/.gitconfig"); + + git_config_get_int32(cfg, "help.autocorrect", &j); + printf("Autocorrect: %d\n", j); + + git_config_get_string(cfg, "user.email", &email); + printf("Email: %s\n", email); + + // Finally, when you're done with the repository, you can free it as well. + git_repository_free(repo); + + return 0; +} + diff --git a/examples/network/.gitignore b/examples/network/.gitignore new file mode 100644 index 000000000..1b48e66ed --- /dev/null +++ b/examples/network/.gitignore @@ -0,0 +1 @@ +/git2 diff --git a/examples/network/Makefile b/examples/network/Makefile new file mode 100644 index 000000000..c21869ac9 --- /dev/null +++ b/examples/network/Makefile @@ -0,0 +1,14 @@ +default: all + +CC = gcc +CFLAGS += -g +CFLAGS += -I../../include -L../../ -lgit2 -lpthread + +OBJECTS = \ + git2.o \ + ls-remote.o \ + fetch.o \ + index-pack.o + +all: $(OBJECTS) + $(CC) $(CFLAGS) -o git2 $(OBJECTS) diff --git a/examples/network/common.h b/examples/network/common.h new file mode 100644 index 000000000..29460bb36 --- /dev/null +++ b/examples/network/common.h @@ -0,0 +1,14 @@ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include + +typedef int (*git_cb)(git_repository *, int , char **); + +int ls_remote(git_repository *repo, int argc, char **argv); +int parse_pkt_line(git_repository *repo, int argc, char **argv); +int show_remote(git_repository *repo, int argc, char **argv); +int fetch(git_repository *repo, int argc, char **argv); +int index_pack(git_repository *repo, int argc, char **argv); + +#endif /* __COMMON_H__ */ diff --git a/examples/network/fetch.c b/examples/network/fetch.c new file mode 100644 index 000000000..8dcb81b1f --- /dev/null +++ b/examples/network/fetch.c @@ -0,0 +1,113 @@ +#include "common.h" +#include +#include +#include +#include +#include + +struct dl_data { + git_remote *remote; + git_off_t *bytes; + git_indexer_stats *stats; + int ret; + int finished; +}; + +static void *download(void *ptr) +{ + struct dl_data *data = (struct dl_data *)ptr; + + // Connect to the remote end specifying that we want to fetch + // information from it. + if (git_remote_connect(data->remote, GIT_DIR_FETCH) < 0) { + data->ret = -1; + goto exit; + } + + // Download the packfile and index it. This function updates the + // amount of received data and the indexer stats which lets you + // inform the user about progress. + if (git_remote_download(data->remote, data->bytes, data->stats) < 0) { + data->ret = -1; + goto exit; + } + + data->ret = 0; + +exit: + data->finished = 1; + pthread_exit(&data->ret); +} + +int update_cb(const char *refname, const git_oid *a, const git_oid *b) +{ + const char *action; + char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; + + git_oid_fmt(b_str, b); + b_str[GIT_OID_HEXSZ] = '\0'; + + if (git_oid_iszero(a)) { + printf("[new] %.20s %s\n", b_str, refname); + } else { + git_oid_fmt(a_str, a); + a_str[GIT_OID_HEXSZ] = '\0'; + printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname); + } + + return 0; +} + +int fetch(git_repository *repo, int argc, char **argv) +{ + git_remote *remote = NULL; + git_off_t bytes = 0; + git_indexer_stats stats; + pthread_t worker; + struct dl_data data; + + // Figure out whether it's a named remote or a URL + printf("Fetching %s\n", argv[1]); + if (git_remote_load(&remote, repo, argv[1]) < 0) { + if (git_remote_new(&remote, repo, NULL, argv[1], NULL) < 0) + return -1; + } + + // Set up the information for the background worker thread + data.remote = remote; + data.bytes = &bytes; + data.stats = &stats; + data.ret = 0; + data.finished = 0; + memset(&stats, 0, sizeof(stats)); + + pthread_create(&worker, NULL, download, &data); + + // Loop while the worker thread is still running. Here we show processed + // and total objects in the pack and the amount of received + // data. Most frontends will probably want to show a percentage and + // the download rate. + do { + usleep(10000); + printf("\rReceived %d/%d objects in %d bytes", stats.processed, stats.total, bytes); + } while (!data.finished); + printf("\rReceived %d/%d objects in %d bytes\n", stats.processed, stats.total, bytes); + + // Disconnect the underlying connection to prevent from idling. + git_remote_disconnect(remote); + + // Update the references in the remote's namespace to point to the + // right commits. This may be needed even if there was no packfile + // to download, which can happen e.g. when the branches have been + // changed but all the neede objects are available locally. + if (git_remote_update_tips(remote, update_cb) < 0) + return -1; + + git_remote_free(remote); + + return 0; + + on_error: + git_remote_free(remote); + return -1; +} diff --git a/examples/network/git2.c b/examples/network/git2.c new file mode 100644 index 000000000..7c02305c4 --- /dev/null +++ b/examples/network/git2.c @@ -0,0 +1,62 @@ +#include +#include + +#include "common.h" + +// This part is not strictly libgit2-dependent, but you can use this +// as a starting point for a git-like tool + +struct { + char *name; + git_cb fn; +} commands[] = { + {"ls-remote", ls_remote}, + {"fetch", fetch}, + {"index-pack", index_pack}, + { NULL, NULL} +}; + +int run_command(git_cb fn, int argc, char **argv) +{ + int error; + git_repository *repo; + +// Before running the actual command, create an instance of the local +// repository and pass it to the function. + + error = git_repository_open(&repo, ".git"); + if (error < 0) + repo = NULL; + + // Run the command. If something goes wrong, print the error message to stderr + error = fn(repo, argc, argv); + if (error < 0) { + if (giterr_last() == NULL) + fprintf(stderr, "Error without message"); + else + fprintf(stderr, "Bad news:\n %s\n", giterr_last()->message); + } + + if(repo) + git_repository_free(repo); + + return !!error; +} + +int main(int argc, char **argv) +{ + int i, error; + + if (argc < 2) { + fprintf(stderr, "usage: %s [repo]\n", argv[0]); + exit(EXIT_FAILURE); + } + + for (i = 0; commands[i].name != NULL; ++i) { + if (!strcmp(argv[1], commands[i].name)) + return run_command(commands[i].fn, --argc, ++argv); + } + + fprintf(stderr, "Command not found: %s\n", argv[1]); + return 1; +} diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c new file mode 100644 index 000000000..5824fc555 --- /dev/null +++ b/examples/network/index-pack.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include "common.h" + +// This could be run in the main loop whilst the application waits for +// the indexing to finish in a worker thread +int index_cb(const git_indexer_stats *stats, void *data) +{ + printf("\rProcessing %d of %d", stats->processed, stats->total); +} + +int index_pack(git_repository *repo, int argc, char **argv) +{ + git_indexer_stream *idx; + git_indexer_stats stats = {0, 0}; + int error, fd; + char hash[GIT_OID_HEXSZ + 1] = {0}; + ssize_t read_bytes; + char buf[512]; + + if (argc < 2) { + fprintf(stderr, "I need a packfile\n"); + return EXIT_FAILURE; + } + + if (git_indexer_stream_new(&idx, ".git") < 0) { + puts("bad idx"); + return -1; + } + + if ((fd = open(argv[1], 0)) < 0) { + perror("open"); + return -1; + } + + do { + read_bytes = read(fd, buf, sizeof(buf)); + if (read_bytes < 0) + break; + + if ((error = git_indexer_stream_add(idx, buf, read_bytes, &stats)) < 0) + goto cleanup; + + printf("\rIndexing %d of %d", stats.processed, stats.total); + } while (read_bytes > 0); + + if (read_bytes < 0) { + error = -1; + perror("failed reading"); + goto cleanup; + } + + if ((error = git_indexer_stream_finalize(idx, &stats)) < 0) + goto cleanup; + + printf("\rIndexing %d of %d\n", stats.processed, stats.total); + + git_oid_fmt(hash, git_indexer_stream_hash(idx)); + puts(hash); + + cleanup: + close(fd); + git_indexer_stream_free(idx); + return error; +} + +int index_pack_old(git_repository *repo, int argc, char **argv) +{ + git_indexer *indexer; + git_indexer_stats stats; + int error; + char hash[GIT_OID_HEXSZ + 1] = {0}; + + if (argc < 2) { + fprintf(stderr, "I need a packfile\n"); + return EXIT_FAILURE; + } + + // Create a new indexer + error = git_indexer_new(&indexer, argv[1]); + if (error < 0) + return error; + + // Index the packfile. This function can take a very long time and + // should be run in a worker thread. + error = git_indexer_run(indexer, &stats); + if (error < 0) + return error; + + // Write the information out to an index file + error = git_indexer_write(indexer); + + // Get the packfile's hash (which should become it's filename) + git_oid_fmt(hash, git_indexer_hash(indexer)); + puts(hash); + + git_indexer_free(indexer); + + return 0; +} diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c new file mode 100644 index 000000000..39cc64725 --- /dev/null +++ b/examples/network/ls-remote.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include "common.h" + +static int show_ref__cb(git_remote_head *head, void *payload) +{ + char oid[GIT_OID_HEXSZ + 1] = {0}; + git_oid_fmt(oid, &head->oid); + printf("%s\t%s\n", oid, head->name); + return 0; +} + +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_new(&remote, repo, NULL, url, NULL); + 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_DIR_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; +} + +int use_remote(git_repository *repo, char *name) +{ + git_remote *remote = NULL; + int error; + + // Find the remote by name + error = git_remote_load(&remote, repo, name); + if (error < 0) + goto cleanup; + + error = git_remote_connect(remote, GIT_DIR_FETCH); + if (error < 0) + goto cleanup; + + error = git_remote_ls(remote, &show_ref__cb, NULL); + +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. + +int ls_remote(git_repository *repo, int argc, char **argv) +{ + int error, i; + + /* 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]); + } + + return error; +} diff --git a/examples/showindex.c b/examples/showindex.c new file mode 100644 index 000000000..7f2130b90 --- /dev/null +++ b/examples/showindex.c @@ -0,0 +1,43 @@ +#include +#include + +int main (int argc, char** argv) +{ + git_repository *repo; + git_index *index; + unsigned int i, e, ecount; + git_index_entry **entries; + git_oid oid; + + char out[41]; + out[40] = '\0'; + + git_repository_open(&repo, "/opt/libgit2-test/.git"); + + git_repository_index(&index, repo); + git_index_read(index); + + ecount = git_index_entrycount(index); + for (i = 0; i < ecount; ++i) { + git_index_entry *e = git_index_get(index, i); + + oid = e->oid; + git_oid_fmt(out, &oid); + + printf("File Path: %s\n", e->path); + printf(" Blob SHA: %s\n", out); + printf("File Size: %d\n", (int)e->file_size); + printf(" Device: %d\n", (int)e->dev); + printf(" Inode: %d\n", (int)e->ino); + printf(" UID: %d\n", (int)e->uid); + printf(" GID: %d\n", (int)e->gid); + printf(" ctime: %d\n", (int)e->ctime.seconds); + printf(" mtime: %d\n", (int)e->mtime.seconds); + printf("\n"); + } + + git_index_free(index); + + git_repository_free(repo); +} + diff --git a/git.git-authors b/git.git-authors index 086ab3e18..026b35fa8 100644 --- a/git.git-authors +++ b/git.git-authors @@ -61,6 +61,7 @@ ok Pierre Habouzit ok Pieter de Bie ok René Scharfe ign Robert Shearman (imap-send) +ok Sebastian Schuberth ok Shawn O. Pearce ok Steffen Prohaska ok Sven Verdoolaege diff --git a/include/git2.h b/include/git2.h index 0d0e54898..d75387318 100644 --- a/include/git2.h +++ b/include/git2.h @@ -1,39 +1,18 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_git_h__ #define INCLUDE_git_git_h__ -#define LIBGIT2_VERSION "0.12.0" -#define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 12 -#define LIBGIT2_VER_REVISION 0 +#include "git2/version.h" #include "git2/common.h" +#include "git2/threads.h" #include "git2/errors.h" -#include "git2/zlib.h" #include "git2/types.h" @@ -43,15 +22,26 @@ #include "git2/repository.h" #include "git2/revwalk.h" +#include "git2/merge.h" #include "git2/refs.h" +#include "git2/reflog.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/remote.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" #endif diff --git a/include/git2/attr.h b/include/git2/attr.h new file mode 100644 index 000000000..8f5a1268d --- /dev/null +++ b/include/git2/attr.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_attr_h__ +#define INCLUDE_git_attr_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/attr.h + * @brief Git attribute management routines + * @defgroup git_attr Git attribute management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * GIT_ATTR_TRUE checks if an attribute is set on. In core git + * parlance, this the value for "Set" attributes. + * + * For example, if the attribute file contains: + * + * *.c foo + * + * Then for file `xyz.c` looking up attribute "foo" gives a value for + * which `GIT_ATTR_TRUE(value)` is true. + */ +#define GIT_ATTR_TRUE(attr) ((attr) == git_attr__true) + +/** + * GIT_ATTR_FALSE checks if an attribute is set off. In core git + * parlance, this is the value for attributes that are "Unset" (not to + * be confused with values that a "Unspecified"). + * + * For example, if the attribute file contains: + * + * *.h -foo + * + * Then for file `zyx.h` looking up attribute "foo" gives a value for + * which `GIT_ATTR_FALSE(value)` is true. + */ +#define GIT_ATTR_FALSE(attr) ((attr) == git_attr__false) + +/** + * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This + * may be due to the attribute not being mentioned at all or because + * the attribute was explicitly set unspecified via the `!` operator. + * + * For example, if the attribute file contains: + * + * *.c foo + * *.h -foo + * onefile.c !foo + * + * Then for `onefile.c` looking up attribute "foo" yields a value with + * `GIT_ATTR_UNSPECIFIED(value)` of true. Also, looking up "foo" on + * file `onefile.rb` or looking up "bar" on any file will all give + * `GIT_ATTR_UNSPECIFIED(value)` of true. + */ +#define GIT_ATTR_UNSPECIFIED(attr) (!(attr) || (attr) == git_attr__unset) + +/** + * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as + * opposed to TRUE, FALSE or UNSPECIFIED). This would be the case if + * for a file with something like: + * + * *.txt eol=lf + * + * Given this, looking up "eol" for `onefile.txt` will give back the + * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true. + */ +#define GIT_ATTR_HAS_VALUE(attr) \ + ((attr) && (attr) != git_attr__unset && \ + (attr) != git_attr__true && (attr) != git_attr__false) + +GIT_EXTERN(const char *) git_attr__true; +GIT_EXTERN(const char *) git_attr__false; +GIT_EXTERN(const char *) git_attr__unset; + +/** + * Check attribute flags: Reading values from index and working directory. + * + * When checking attributes, it is possible to check attribute files + * in both the working directory (if there is one) and the index (if + * there is one). You can explicitly choose where to check and in + * which order using the following flags. + * + * Core git usually checks the working directory then the index, + * except during a checkout when it checks the index first. It will + * use index only for creating archives or for a bare repo (if an + * index has been specified for the bare repo). + */ +#define GIT_ATTR_CHECK_FILE_THEN_INDEX 0 +#define GIT_ATTR_CHECK_INDEX_THEN_FILE 1 +#define GIT_ATTR_CHECK_INDEX_ONLY 2 + +/** + * Check attribute flags: Using the system attributes file. + * + * Normally, attribute checks include looking in the /etc (or system + * equivalent) directory for a `gitattributes` file. Passing this + * flag will cause attribute checks to ignore that file. + */ +#define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2) + +/** + * Look up the value of one git attribute for path. + * + * @param value_out Output of the value of the attribute. Use the GIT_ATTR_... + * macros to test for TRUE, FALSE, UNSPECIFIED, etc. or just + * use the string value for attributes set to a value. You + * should NOT modify or free this value. + * @param repo The repository containing the path. + * @param flags A combination of GIT_ATTR_CHECK... flags. + * @param path The path to check for attributes. Relative paths are + * interpreted relative to the repo root. The file does + * not have to exist, but if it does not, then it will be + * treated as a plain file (not a directory). + * @param name The name of the attribute to look up. + */ +GIT_EXTERN(int) git_attr_get( + const char **value_out, + git_repository *repo, + uint32_t flags, + const char *path, + const char *name); + +/** + * Look up a list of git attributes for path. + * + * Use this if you have a known list of attributes that you want to + * look up in a single call. This is somewhat more efficient than + * calling `git_attr_get()` multiple times. + * + * For example, you might write: + * + * const char *attrs[] = { "crlf", "diff", "foo" }; + * const char **values[3]; + * git_attr_get_many(values, repo, 0, "my/fun/file.c", 3, attrs); + * + * Then you could loop through the 3 values to get the settings for + * the three attributes you asked about. + * + * @param values An array of num_attr entries that will have string + * pointers written into it for the values of the attributes. + * You should not modify or free the values that are written + * into this array (although of course, you should free the + * array itself if you allocated it). + * @param repo The repository containing the path. + * @param flags A combination of GIT_ATTR_CHECK... flags. + * @param path The path inside the repo to check attributes. This + * does not have to exist, but if it does not, then + * it will be treated as a plain file (i.e. not a directory). + * @param num_attr The number of attributes being looked up + * @param names An array of num_attr strings containing attribute names. + */ +GIT_EXTERN(int) git_attr_get_many( + const char **values_out, + git_repository *repo, + uint32_t flags, + const char *path, + size_t num_attr, + const char **names); + +/** + * Loop over all the git attributes for a path. + * + * @param repo The repository containing the path. + * @param flags A combination of GIT_ATTR_CHECK... flags. + * @param path The path inside the repo to check attributes. This + * does not have to exist, but if it does not, then + * it will be treated as a plain file (i.e. not a directory). + * @param callback The function that will be invoked on each attribute + * and attribute value. The name parameter will be the name + * of the attribute and the value will be the value it is + * set to, including possibly NULL if the attribute is + * explicitly set to UNSPECIFIED using the ! sign. This + * will be invoked only once per attribute name, even if + * there are multiple rules for a given file. The highest + * priority rule will be used. + * @param payload Passed on as extra parameter to callback function. + */ +GIT_EXTERN(int) git_attr_foreach( + git_repository *repo, + uint32_t flags, + const char *path, + int (*callback)(const char *name, const char *value, void *payload), + void *payload); + +/** + * Flush the gitattributes cache. + * + * Call this if you have reason to believe that the attributes files on + * disk no longer match the cached contents of memory. This will cause + * the attributes files to be reloaded the next time that an attribute + * access function is called. + */ +GIT_EXTERN(void) git_attr_cache_flush( + git_repository *repo); + +/** + * Add a macro definition. + * + * Macros will automatically be loaded from the top level `.gitattributes` + * file of the repository (plus the build-in "binary" macro). This + * function allows you to add others. For example, to add the default + * macro, you would call: + * + * git_attr_add_macro(repo, "binary", "-diff -crlf"); + */ +GIT_EXTERN(int) git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values); + +/** @} */ +GIT_END_DECL +#endif + diff --git a/include/git2/blob.h b/include/git2/blob.h index 0e05d6f89..544dc7c41 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_blob_h__ #define INCLUDE_git_blob_h__ @@ -45,17 +27,34 @@ GIT_BEGIN_DECL * @param blob pointer to the looked up blob * @param repo the repo to use when locating the blob. * @param id identity of the blob to locate. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_INLINE(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git_oid *id) { return git_object_lookup((git_object **)blob, repo, id, GIT_OBJ_BLOB); } +/** + * Lookup a blob object from a repository, + * given a prefix of its identifier (short id). + * + * @see git_object_lookup_prefix + * + * @param blob pointer to the looked up blob + * @param repo the repo to use when locating the blob. + * @param id identity of the blob to locate. + * @param len the length of the short identifier + * @return 0 or an error code + */ +GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, unsigned int len) +{ + return git_object_lookup_prefix((git_object **)blob, repo, id, len, GIT_OBJ_BLOB); +} + /** * Close an open blob * - * This is a wrapper around git_object_close() + * This is a wrapper around git_object_free() * * IMPORTANT: * It *is* necessary to call this method when you stop @@ -64,9 +63,9 @@ GIT_INLINE(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git * @param blob the blob to close */ -GIT_INLINE(void) git_blob_close(git_blob *blob) +GIT_INLINE(void) git_blob_free(git_blob *blob) { - git_object_close((git_object *) blob); + git_object_free((git_object *) blob); } @@ -89,7 +88,7 @@ GIT_EXTERN(const void *) git_blob_rawcontent(git_blob *blob); * @param blob pointer to the blob * @return size on bytes */ -GIT_EXTERN(int) git_blob_rawsize(git_blob *blob); +GIT_EXTERN(size_t) git_blob_rawsize(git_blob *blob); /** * Read a file from the working folder of a repository @@ -100,10 +99,64 @@ GIT_EXTERN(int) git_blob_rawsize(git_blob *blob); * this repository cannot be bare * @param path file from which the blob will be created, * relative to the repository's working dir - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path); +/** + * Read a file from the filesystem and write its content + * to the Object Database as a loose blob + * + * @param oid return the id of the written blob + * @param repo repository where the blob will be written. + * this repository can be bare or not + * @param path file from which the blob will be created + * @return 0 or an error code + */ +GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path); + +/** + * Write a loose blob to the Object Database from a + * provider of chunks of data. + * + * Provided the `hintpath` parameter is filled, its value + * will help to determine what git filters should be applied + * to the object before it can be placed to the object database. + * + * + * The implementation of the callback has to respect the + * following rules: + * + * - `content` will have to be filled by the consumer. The maximum number + * of bytes that the buffer can accept per call is defined by the + * `max_length` parameter. Allocation and freeing of the buffer will be taken + * care of by the function. + * + * - The callback is expected to return the number of bytes + * that `content` have been filled with. + * + * - When there is no more data to stream, the callback should + * return 0. This will prevent it from being invoked anymore. + * + * - When an error occurs, the callback should return -1. + * + * + * @param oid Return the id of the written blob + * + * @param repo repository where the blob will be written. + * This repository can be bare or not. + * + * @param hintpath if not NULL, will help selecting the filters + * to apply onto the content of the blob to be created. + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_blob_create_fromchunks( + git_oid *oid, + git_repository *repo, + const char *hintpath, + int (*source_cb)(char *content, size_t max_length, void *payload), + void *payload); /** * Write an in-memory buffer to the ODB as a blob @@ -112,7 +165,7 @@ GIT_EXTERN(int) git_blob_create_fromfile(git_oid *oid, git_repository *repo, con * @param repo repository where to blob will be written * @param buffer data to be written into the blob * @param len length of the data - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len); diff --git a/include/git2/branch.h b/include/git2/branch.h new file mode 100644 index 000000000..e2432bcfc --- /dev/null +++ b/include/git2/branch.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_branch_h__ +#define INCLUDE_git_branch_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/branch.h + * @brief Git branch parsing routines + * @defgroup git_branch Git branch management + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new branch pointing at a target commit + * + * A new direct reference will be created pointing to + * this target commit. If `force` is true and a reference + * already exists with the given name, it'll be replaced. + * + * @param oid_out Pointer where to store the OID of the target commit. + * + * @param repo Repository where to store the branch. + * + * @param branch_name Name for the branch; this name is + * validated for consistency. It should also not conflict with + * an already existing branch name. + * + * @param target Object to which this branch should point. This object + * must belong to the given `repo` and can either be a git_commit or a + * git_tag. When a git_tag is being passed, it should be dereferencable + * to a git_commit which oid will be used as the target of the branch. + * + * @param force Overwrite existing branch. + * + * @return 0 or an error code. + * A proper reference is written in the refs/heads namespace + * pointing to the provided target commit. + */ +GIT_EXTERN(int) git_branch_create( + git_oid *oid_out, + git_repository *repo, + const char *branch_name, + const git_object *target, + int force); + +/** + * Delete an existing branch reference. + * + * @param repo Repository where lives the branch. + * + * @param branch_name Name of the branch to be deleted; + * this name is validated for consistency. + * + * @param branch_type Type of the considered branch. This should + * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE. + * + * @return 0 on success, GIT_ENOTFOUND if the branch + * doesn't exist or an error code. + */ +GIT_EXTERN(int) git_branch_delete( + git_repository *repo, + const char *branch_name, + git_branch_t branch_type); + +/** + * Fill a list with all the branches in the Repository + * + * The string array will be filled with the names of the + * matching branches; these values are owned by the user and + * should be free'd manually when no longer needed, using + * `git_strarray_free`. + * + * @param branch_names Pointer to a git_strarray structure + * where the branch names will be stored. + * + * @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. + * + * @return 0 or an error code. + */ +GIT_EXTERN(int) git_branch_list( + git_strarray *branch_names, + git_repository *repo, + unsigned int list_flags); + +/** + * Move/rename an existing branch reference. + * + * @param repo Repository where lives the branch. + * + * @param old_branch_name Current name of the branch to be moved; + * this name is validated for consistency. + * + * @param new_branch_name Target name of the branch once the move + * is performed; this name is validated for consistency. + * + * @param force Overwrite existing branch. + * + * @return 0 on success, GIT_ENOTFOUND if the branch + * doesn't exist or an error code. + */ +GIT_EXTERN(int) git_branch_move( + git_repository *repo, + const char *old_branch_name, + const char *new_branch_name, + int force); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 3687d9460..a6d9bb0e3 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_commit_h__ #define INCLUDE_git_commit_h__ @@ -44,19 +26,37 @@ GIT_BEGIN_DECL * * @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 on success; error code otherwise + * @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_INLINE(int) git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id) { return git_object_lookup((git_object **)commit, repo, id, GIT_OBJ_COMMIT); } +/** + * Lookup a commit object from a repository, + * given a prefix of its identifier (short id). + * + * @see git_object_lookup_prefix + * + * @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. + * @param len the length of the short identifier + * @return 0 or an error code + */ +GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, unsigned len) +{ + return git_object_lookup_prefix((git_object **)commit, repo, id, len, GIT_OBJ_COMMIT); +} + /** * Close an open commit * - * This is a wrapper around git_object_close() + * This is a wrapper around git_object_free() * * IMPORTANT: * It *is* necessary to call this method when you stop @@ -65,9 +65,9 @@ GIT_INLINE(int) git_commit_lookup(git_commit **commit, git_repository *repo, con * @param commit the commit to close */ -GIT_INLINE(void) git_commit_close(git_commit *commit) +GIT_INLINE(void) git_commit_free(git_commit *commit) { - git_object_close((git_object *) commit); + git_object_free((git_object *) commit); } /** @@ -79,12 +79,16 @@ GIT_INLINE(void) git_commit_close(git_commit *commit) GIT_EXTERN(const git_oid *) git_commit_id(git_commit *commit); /** - * Get the short (one line) message of a commit. + * Get the encoding for the message of a commit, + * as a string representing a standard encoding name. + * + * The encoding may be NULL if the `encoding` header + * in the commit is missing; in that case UTF-8 is assumed. * * @param commit a previously loaded commit. - * @return the short message of a commit + * @return NULL, or the encoding */ -GIT_EXTERN(const char *) git_commit_message_short(git_commit *commit); +GIT_EXTERN(const char *) git_commit_message_encoding(git_commit *commit); /** * Get the full message of a commit. @@ -131,7 +135,7 @@ GIT_EXTERN(const git_signature *) git_commit_author(git_commit *commit); * * @param tree_out pointer where to store the tree object * @param commit a previously loaded commit. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, git_commit *commit); @@ -159,7 +163,7 @@ GIT_EXTERN(unsigned int) git_commit_parentcount(git_commit *commit); * @param parent Pointer where to store the parent commit * @param commit a previously loaded commit. * @param n the position of the parent (from 0 to `parentcount`) - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n); @@ -175,8 +179,11 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n); /** - * Create a new commit in the repository + * Create a new commit in the repository using `git_object` + * instances as parameters. * + * The message will be cleaned up from excess whitespace + * it will be made sure that the last line ends with a '\n'. * * @param oid Pointer where to store the OID of the * newly created commit @@ -187,28 +194,34 @@ GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned i * will be updated to point to this commit. If the reference * is not direct, it will be resolved to a direct reference. * Use "HEAD" to update the HEAD of the current branch and - * make it point to this commit + * make it point to this commit. If the reference doesn't + * exist yet, it will be created. * * @param author Signature representing the author and the authory * time of this commit * * @param committer Signature representing the committer and the - * commit time of this commit + * commit time of this commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. * * @param message Full message for this commit * - * @param tree_oid Object ID of the tree for this commit. Note that - * no validation is performed on this OID. Use the _o variants of - * this method to assure a proper tree is passed to the commit. + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. * * @param parent_count Number of parents for this commit * - * @param parents Array of pointers to parent OIDs for this commit. - * Note that no validation is performed on these OIDs. Use the _o - * variants of this method to assure that are parents for the commit - * are proper objects. + * @param parents[] Array of `parent_count` pointers to `git_commit` + * objects that will be used as the parents for this commit. This + * array may be NULL if `parent_count` is 0 (root commit). All the + * given commits must be owned by the `repo`. * - * @return 0 on success; error code otherwise + * @return 0 or an error code * The created commit will be written to the Object Database and * the given reference will be updated to point to it */ @@ -218,39 +231,18 @@ GIT_EXTERN(int) git_commit_create( const char *update_ref, const git_signature *author, const git_signature *committer, - const char *message, - const git_oid *tree_oid, - int parent_count, - const git_oid *parent_oids[]); - -/** - * Create a new commit in the repository using `git_object` - * instances as parameters. - * - * The `tree_oid` and `parent_oids` paremeters now take a instance - * of `git_tree` and `git_commit`, respectively. - * - * All other parameters remain the same - * - * @see git_commit_create - */ -GIT_EXTERN(int) git_commit_create_o( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, + const char *message_encoding, const char *message, const git_tree *tree, int parent_count, const git_commit *parents[]); /** - * Create a new commit in the repository using `git_object` - * instances and a variable argument list. + * Create a new commit in the repository using a variable + * argument list. * - * The `tree_oid` paremeter now takes a instance - * of `const git_tree *`. + * The message will be cleaned up from excess whitespace + * it will be made sure that the last line ends with a '\n'. * * The parents for the commit are specified as a variable * list of pointers to `const git_commit *`. Note that this @@ -261,39 +253,15 @@ GIT_EXTERN(int) git_commit_create_o( * * @see git_commit_create */ -GIT_EXTERN(int) git_commit_create_ov( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message, - const git_tree *tree, - int parent_count, - ...); - - -/** - * Create a new commit in the repository using - * a variable argument list. - * - * The parents for the commit are specified as a variable - * list of pointers to `const git_oid *`. Note that this - * is a convenience method which may not be safe to export - * for certain languages or compilers - * - * All other parameters remain the same - * - * @see git_commit_create - */ GIT_EXTERN(int) git_commit_create_v( git_oid *oid, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, + const char *message_encoding, const char *message, - const git_oid *tree_oid, + const git_tree *tree, int parent_count, ...); diff --git a/include/git2/common.h b/include/git2/common.h index 9a27ac2e5..0e9379804 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -1,67 +1,42 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_common_h__ #define INCLUDE_git_common_h__ -#include "thread-utils.h" #include #include -#ifdef __cplusplus -# define GIT_BEGIN_DECL extern "C" { -# define GIT_END_DECL } +#ifdef _MSC_VER +# include "inttypes.h" #else - /** Start declarations in C mode */ -# define GIT_BEGIN_DECL /* empty */ - /** End declarations in C mode */ -# define GIT_END_DECL /* empty */ +# include +#endif + +#ifdef __cplusplus +# define GIT_BEGIN_DECL extern "C" { +# define GIT_END_DECL } +#else + /** Start declarations in C mode */ +# define GIT_BEGIN_DECL /* empty */ + /** End declarations in C mode */ +# define GIT_END_DECL /* empty */ #endif /** Declare a public function exported for application use. */ -#ifdef __GNUC__ +#if __GNUC__ >= 4 # define GIT_EXTERN(type) extern \ - __attribute__((visibility("default"))) \ - type + __attribute__((visibility("default"))) \ + type #elif defined(_MSC_VER) # define GIT_EXTERN(type) __declspec(dllexport) type #else # define GIT_EXTERN(type) extern type #endif -/** Declare a public TLS symbol exported for application use. */ -#ifdef __GNUC__ -# define GIT_EXTERN_TLS(type) extern \ - __attribute__((visibility("default"))) \ - GIT_TLS \ - type -#elif defined(_MSC_VER) -# define GIT_EXTERN_TLS(type) __declspec(dllexport) GIT_TLS type -#else -# define GIT_EXTERN_TLS(type) extern GIT_TLS type -#endif - /** Declare a function as always inlined. */ #if defined(_MSC_VER) # define GIT_INLINE(type) static __inline type @@ -76,6 +51,10 @@ # define GIT_FORMAT_PRINTF(a,b) /* empty */ #endif +#if (defined(_WIN32)) && !defined(__CYGWIN__) +#define GIT_WIN32 1 +#endif + /** * @file git2/common.h * @brief Git common platform definitions @@ -86,12 +65,39 @@ GIT_BEGIN_DECL +/** + * The separator used in path list strings (ie like in the PATH + * environment variable). A semi-colon ";" is used on Windows, and + * a colon ":" for all other systems. + */ +#ifdef GIT_WIN32 +#define GIT_PATH_LIST_SEPARATOR ';' +#else +#define GIT_PATH_LIST_SEPARATOR ':' +#endif + +/** + * The maximum length of a valid git path. + */ +#define GIT_PATH_MAX 4096 + typedef struct { char **strings; size_t count; } git_strarray; GIT_EXTERN(void) git_strarray_free(git_strarray *array); +GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src); + +/** + * Return the version of the libgit2 library + * being currently used. + * + * @param major Store the major version number + * @param minor Store the minor version number + * @param rev Store the revision (patch) number + */ +GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); /** @} */ GIT_END_DECL diff --git a/include/git2/config.h b/include/git2/config.h index 78a1f622a..36946c4a5 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_config_h__ #define INCLUDE_git_config_h__ @@ -47,56 +29,146 @@ struct git_config_file { /* Open means open the file/database and parse if necessary */ int (*open)(struct git_config_file *); int (*get)(struct git_config_file *, const char *key, const char **value); + int (*get_multivar)(struct git_config_file *, const char *key, const char *regexp, int (*fn)(const char *, void *), void *data); int (*set)(struct git_config_file *, const char *key, const char *value); - int (*foreach)(struct git_config_file *, int (*fn)(const char *, void *), void *data); + int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); + int (*del)(struct git_config_file *, const char *key); + int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data); void (*free)(struct git_config_file *); }; +typedef enum { + GIT_CVAR_FALSE = 0, + GIT_CVAR_TRUE = 1, + GIT_CVAR_INT32, + GIT_CVAR_STRING +} git_cvar_t; + +typedef struct { + git_cvar_t cvar_type; + const char *str_match; + int map_value; +} git_cvar_map; + +/** + * Locate the path to the global configuration file + * + * The user or global configuration file is usually + * located in `$HOME/.gitconfig`. + * + * This method will try to guess the full path to that + * file, if the file exists. The returned path + * may be used on any `git_config` call to load the + * global configuration file. + * + * @param global_config_path Buffer of GIT_PATH_MAX length to store the path + * @return 0 if a global configuration file has been + * found. Its path will be stored in `buffer`. + */ +GIT_EXTERN(int) git_config_find_global(char *global_config_path, size_t length); + +/** + * Locate the path to the system configuration file + * + * If /etc/gitconfig doesn't exist, it will look for + * %PROGRAMFILES%\Git\etc\gitconfig. + + * @param system_config_path Buffer of GIT_PATH_MAX length to store the path + * @return 0 if a system configuration file has been + * found. Its path will be stored in `buffer`. + */ +GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length); + +/** + * Open the global configuration file + * + * Utility wrapper that calls `git_config_find_global` + * and opens the located file, if it exists. + * + * @param out Pointer to store the config instance + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_open_global(git_config **out); + /** * Create a configuration file backend for ondisk files * * These are the normal `.gitconfig` files that Core Git - * processes. + * processes. Note that you first have to add this file to a + * configuration object before you can query it for configuration + * variables. * * @param out the new backend - * @path where the config file is located + * @param path where the config file is located */ GIT_EXTERN(int) git_config_file__ondisk(struct git_config_file **out, const char *path); /** - * Allocate a new configuration + * Allocate a new configuration object + * + * This object is empty, so you have to add a file to it before you + * can do anything with it. + * + * @param out pointer to the new configuration + * @return 0 or an error code */ GIT_EXTERN(int) git_config_new(git_config **out); /** - * Open a configuration file + * Add a generic config file instance to an existing config * - * @param cfg_out pointer to the configuration data - * @param path where to load the confiration from - */ -GIT_EXTERN(int) git_config_open_file(git_config **cfg_out, const char *path); - -/** - * Open the global configuration file at $HOME/.gitconfig + * Note that the configuration object will free the file + * automatically. * - * @param cfg pointer to the configuration - */ -GIT_EXTERN(int) git_config_open_global(git_config **cfg); - -/** - * Add a config backend to an existing instance + * Further queries on this config object will access each + * of the config file instances in order (instances with + * a higher priority will be accessed first). * - * Note that the configuration will call the backend's ->free() - * function. - * - * @param cfg the configuration to add the backend to - * @param backend the backend to add + * @param cfg the configuration to add the file to + * @param file the configuration file (backend) to add * @param priority the priority the backend should have + * @return 0 or an error code */ GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int priority); /** - * Free the configuration and its associated memory + * Add an on-disk config file instance to an existing config + * + * The on-disk file pointed at by `path` will be opened and + * parsed; it's expected to be a native Git config file following + * the default Git config syntax (see man git-config). + * + * Note that the configuration object will free the file + * automatically. + * + * Further queries on this config object will access each + * of the config file instances in order (instances with + * a higher priority will be accessed first). + * + * @param cfg the configuration to add the file to + * @param path path to the configuration file (backend) to add + * @param priority the priority the backend should have + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, int priority); + + +/** + * Create a new config instance containing a single on-disk file + * + * This method is a simple utility wrapper for the following sequence + * of calls: + * - git_config_new + * - git_config_add_file_ondisk + * + * @param cfg The configuration instance to create + * @param path Path to the on-disk file to open + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path); + +/** + * Free the configuration and its associated memory and files * * @param cfg the configuration to free */ @@ -105,22 +177,22 @@ GIT_EXTERN(void) git_config_free(git_config *cfg); /** * Get the value of an integer config variable. * + * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out); +GIT_EXTERN(int) git_config_get_int32(int32_t *out, git_config *cfg, const char *name); /** * Get the value of a long integer config variable. * + * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int *out); +GIT_EXTERN(int) git_config_get_int64(int64_t *out, git_config *cfg, const char *name); /** * Get the value of a boolean config variable. @@ -128,12 +200,12 @@ GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int * This function uses the usual C convention of 0 being false and * anything else true. * + * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out); +GIT_EXTERN(int) git_config_get_bool(int *out, git_config *cfg, const char *name); /** * Get the value of a string config variable. @@ -141,32 +213,46 @@ GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out) * The string is owned by the variable and should not be freed by the * user. * + * @param out pointer to the variable's value * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable's value - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out); +GIT_EXTERN(int) git_config_get_string(const char **out, git_config *cfg, const char *name); + +/** + * Get each value of a multivar. + * + * The callback will be called on each variable found + * + * @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 + * @param fn the function to be called on each value of the variable + * @param data opaque pointer to pass to the callback + */ +GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, int (*fn)(const char *, void *), void *data); /** * Set the value of an integer config variable. * * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @return GIT_SUCCESS on success; error code otherwise + * @param value Integer value for the variable + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value); +GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t value); /** * Set the value of a long integer config variable. * * @param cfg where to look for the variable * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @return GIT_SUCCESS on success; error code otherwise + * @param value Long integer value for the variable + * @return 0 or an error code */ -GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int value); +GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t value); /** * Set the value of a boolean config variable. @@ -174,7 +260,7 @@ GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int * @param cfg where to look for the variable * @param name the variable's name * @param value the value to store - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value); @@ -187,24 +273,83 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value * @param cfg where to look for the variable * @param name the variable's name * @param value the string to store. - * @return GIT_SUCCESS on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value); + +/** + * Set a multivar + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param regexp a regular expression to indicate which values to replace + * @param value the new value. + */ +GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); + +/** + * Delete a config variable + * + * @param cfg the configuration + * @param name the variable to delete + */ +GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name); + /** * Perform an operation on each config variable. * - * The callback is passed a pointer to a config variable name and the - * data pointer passed to this function. As soon as one of the - * callback functions returns something other than 0, this function - * returns that value. + * The callback receives the normalized name and value of each variable + * in the config backend, and the data pointer passed to this function. + * As soon as one of the callback functions returns something other than 0, + * this function returns that value. * * @param cfg where to get the variables from * @param callback the function to call on each variable - * @param data the data to pass to the callback - * @return GIT_SUCCESS or the return value of the callback which didn't return 0 + * @param payload the data to pass to the callback + * @return 0 or the return value of the callback which didn't return 0 */ -GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(const char *, void *data), void *data); +GIT_EXTERN(int) git_config_foreach( + git_config *cfg, + int (*callback)(const char *var_name, const char *value, void *payload), + void *payload); + + +/** + * Query the value of a config variable and return it mapped to + * an integer constant. + * + * This is a helper method to easily map different possible values + * to a variable to integer constants that easily identify them. + * + * A mapping array looks as follows: + * + * git_cvar_map autocrlf_mapping[3] = { + * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}, + * {GIT_CVAR_STRING, "default", GIT_AUTO_CRLF_DEFAULT}}; + * + * On any "false" value for the variable (e.g. "false", "FALSE", "no"), the + * mapping will store `GIT_AUTO_CRLF_FALSE` in the `out` parameter. + * + * The same thing applies for any "true" value such as "true", "yes" or "1", storing + * the `GIT_AUTO_CRLF_TRUE` variable. + * + * Otherwise, if the value matches the string "input" (with case insensitive comparison), + * the given constant will be stored in `out`, and likewise for "default". + * + * If not a single match can be made to store in `out`, an error code will be + * returned. + * + * @param out place to store the result of the mapping + * @param cfg config file to get the variables from + * @param name name of the config variable to lookup + * @param maps array of `git_cvar_map` objects specifying the possible mappings + * @param map_n number of mapping objects in `maps` + * @return 0 on success, error code otherwise + */ +GIT_EXTERN(int) git_config_get_mapped(int *out, git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n); /** @} */ GIT_END_DECL diff --git a/include/git2/diff.h b/include/git2/diff.h new file mode 100644 index 000000000..a0e8ad6d2 --- /dev/null +++ b/include/git2/diff.h @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_diff_h__ +#define INCLUDE_git_diff_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "tree.h" +#include "refs.h" + +/** + * @file git2/diff.h + * @brief Git tree and file differencing routines. + * + * 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. + * + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_DIFF_NORMAL = 0, + GIT_DIFF_REVERSE = (1 << 0), + GIT_DIFF_FORCE_TEXT = (1 << 1), + GIT_DIFF_IGNORE_WHITESPACE = (1 << 2), + GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), + GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), + GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), + GIT_DIFF_PATIENCE = (1 << 6), + GIT_DIFF_INCLUDE_IGNORED = (1 << 7), + GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), + GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), + GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), +}; + +/** + * Structure describing options about how the diff should be executed. + * + * Setting all values of the structure to zero will yield the default + * values. Similarly, passing NULL for the options structure will + * give the defaults. The default values are marked below. + * + * @todo Most of the parameters here are not actually supported at this time. + */ +typedef struct { + uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ + uint16_t context_lines; /**< defaults to 3 */ + uint16_t interhunk_lines; /**< defaults to 3 */ + char *old_prefix; /**< defaults to "a" */ + char *new_prefix; /**< defaults to "b" */ + git_strarray pathspec; /**< defaults to show all paths */ +} git_diff_options; + +/** + * The diff list object that contains all individual file deltas. + */ +typedef struct git_diff_list git_diff_list; + +enum { + GIT_DIFF_FILE_VALID_OID = (1 << 0), + GIT_DIFF_FILE_FREE_PATH = (1 << 1), + GIT_DIFF_FILE_BINARY = (1 << 2), + GIT_DIFF_FILE_NOT_BINARY = (1 << 3), + GIT_DIFF_FILE_FREE_DATA = (1 << 4), + GIT_DIFF_FILE_UNMAP_DATA = (1 << 5) +}; + +/** + * What type of change is described by a git_diff_delta? + */ +typedef enum { + GIT_DELTA_UNMODIFIED = 0, + GIT_DELTA_ADDED = 1, + GIT_DELTA_DELETED = 2, + GIT_DELTA_MODIFIED = 3, + GIT_DELTA_RENAMED = 4, + GIT_DELTA_COPIED = 5, + GIT_DELTA_IGNORED = 6, + GIT_DELTA_UNTRACKED = 7 +} git_delta_t; + +/** + * Description of one side of a diff. + */ +typedef struct { + git_oid oid; + char *path; + uint16_t mode; + git_off_t size; + unsigned int flags; +} git_diff_file; + +/** + * Description of changes to one file. + * + * When iterating over a diff list object, this will generally be passed to + * most callback functions and you can use the contents to understand + * exactly what has changed. + * + * Under some circumstances, not all fields will be filled in, but the code + * generally tries to fill in as much as possible. One example is that the + * "binary" field will not actually look at file contents if you do not + * pass in hunk and/or line callbacks to the diff foreach iteration function. + * It will just use the git attributes for those files. + */ +typedef struct { + git_diff_file old_file; + git_diff_file new_file; + git_delta_t status; + unsigned int similarity; /**< for RENAMED and COPIED, value 0-100 */ + int binary; +} git_diff_delta; + +/** + * When iterating over a diff, callback that will be made per file. + */ +typedef int (*git_diff_file_fn)( + void *cb_data, + git_diff_delta *delta, + float progress); + +/** + * Structure describing a hunk of a diff. + */ +typedef struct { + int old_start; + int old_lines; + int new_start; + int new_lines; +} git_diff_range; + +/** + * When iterating over a diff, callback that will be made per hunk. + */ +typedef int (*git_diff_hunk_fn)( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len); + +/** + * Line origin constants. + * + * These values describe where a line came from and will be passed to + * the git_diff_data_fn 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. + */ +enum { + /* these values will be sent to `git_diff_data_fn` along with the line */ + GIT_DIFF_LINE_CONTEXT = ' ', + GIT_DIFF_LINE_ADDITION = '+', + GIT_DIFF_LINE_DELETION = '-', + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */ + GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ + /* these values will only be sent to a `git_diff_data_fn` when the content + * of a diff is being formatted (eg. through git_diff_print_patch() or + * git_diff_print_compact(), for instance). + */ + GIT_DIFF_LINE_FILE_HDR = 'F', + GIT_DIFF_LINE_HUNK_HDR = 'H', + GIT_DIFF_LINE_BINARY = 'B' +}; + +/** + * When iterating over a diff, callback that will be made per text diff + * line. In this context, the provided range will be NULL. + * + * When printing a diff, callback that will be made to output each line + * of text. This uses some extra GIT_DIFF_LINE_... constants for output + * of lines of file and hunk headers. + */ +typedef int (*git_diff_data_fn)( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char *content, + size_t content_len); + +/** @name Diff List Generator Functions + * + * These are the functions you would use to create (or destroy) a + * git_diff_list from various objects in a repository. + */ +/**@{*/ + +/** + * Deallocate a diff list. + */ +GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); + +/** + * Compute a difference between two tree objects. + * + * @param repo The repository containing the trees. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param new_tree A git_tree object to diff to. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_tree_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_tree *new_tree, + git_diff_list **diff); + +/** + * Compute a difference between a tree and the index. + * + * @param repo The repository containing the tree and index. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_diff_list **diff); + +/** + * Compute a difference between the working directory and the index. + * + * @param repo The repository. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_workdir_to_index( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_diff_list **diff); + +/** + * Compute a difference between the working directory and a tree. + * + * This returns strictly the differences between the tree and the + * files contained in the working directory, regardless of the state + * of files in the index. There is no direct equivalent in C git. + * + * This is *NOT* the same as 'git diff HEAD' or 'git diff '. Those + * commands diff the tree, the index, and the workdir. To emulate those + * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`, + * then call `git_diff_merge` on the results. + * + * @param repo The repository containing the tree. + * @param opts Structure with options to influence diff or NULL for defaults. + * @param old_tree A git_tree object to diff from. + * @param diff A pointer to a git_diff_list pointer that will be allocated. + */ +GIT_EXTERN(int) git_diff_workdir_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_diff_list **diff); + +/** + * Merge one diff list 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. + * 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 + * pending DELETE in the middle, then it will show as deleted). + * + * @param onto Diff to merge into. + * @param from Diff to merge. + */ +GIT_EXTERN(int) git_diff_merge( + git_diff_list *onto, + const git_diff_list *from); + +/**@}*/ + + +/** @name Diff List Processor Functions + * + * These are the functions you apply to a diff list to process it + * or read it in some way. + */ +/**@{*/ + +/** + * Iterate over a diff list issuing callbacks. + * + * If the hunk and/or line callbacks are not NULL, then this will calculate + * text diffs for all files it thinks are not binary. If those are both + * NULL, then this will not bother with the text diffs, so it can be + * efficient. + */ +GIT_EXTERN(int) git_diff_foreach( + git_diff_list *diff, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb); + +/** + * Iterate over a diff generating text output like "git diff --name-status". + */ +GIT_EXTERN(int) git_diff_print_compact( + git_diff_list *diff, + void *cb_data, + git_diff_data_fn print_cb); + +/** + * Iterate over a diff generating text output like "git diff". + * + * This is a super easy way to generate a patch from a diff. + */ +GIT_EXTERN(int) git_diff_print_patch( + git_diff_list *diff, + void *cb_data, + git_diff_data_fn print_cb); + +/**@}*/ + + +/* + * Misc + */ + +/** + * Directly run a text diff on two blobs. + * + * Compared to a file, a blob lacks some contextual information. As such, the + * `git_diff_file` parameters of the callbacks will be filled accordingly to the following: + * `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid` + * will be set to 0. + * + * When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary + * attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made. + */ +GIT_EXTERN(int) git_diff_blobs( + git_blob *old_blob, + git_blob *new_blob, + git_diff_options *options, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb); + +GIT_END_DECL + +/** @} */ + +#endif diff --git a/include/git2/errors.h b/include/git2/errors.h index 3ed5cd680..ccbc9fcf4 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_errors_h__ #define INCLUDE_git_errors_h__ @@ -35,113 +17,92 @@ */ GIT_BEGIN_DECL -typedef enum { +#ifdef GIT_OLD_ERRORS +enum { GIT_SUCCESS = 0, - GIT_ERROR = -1, - - /** Input was not a properly formatted Git object id. */ GIT_ENOTOID = -2, - - /** Input does not exist in the scope searched. */ GIT_ENOTFOUND = -3, - - /** Not enough space available. */ GIT_ENOMEM = -4, - - /** Consult the OS error information. */ GIT_EOSERR = -5, - - /** The specified object is of invalid type */ GIT_EOBJTYPE = -6, - - /** The specified repository is invalid */ GIT_ENOTAREPO = -7, - - /** The object type is invalid or doesn't match */ GIT_EINVALIDTYPE = -8, - - /** The object cannot be written because it's missing internal data */ GIT_EMISSINGOBJDATA = -9, - - /** The packfile for the ODB is corrupted */ GIT_EPACKCORRUPTED = -10, - - /** Failed to acquire or release a file lock */ GIT_EFLOCKFAIL = -11, - - /** The Z library failed to inflate/deflate an object's data */ GIT_EZLIB = -12, - - /** The queried object is currently busy */ GIT_EBUSY = -13, - - /** The index file is not backed up by an existing repository */ GIT_EBAREINDEX = -14, - - /** The name of the reference is not valid */ GIT_EINVALIDREFNAME = -15, - - /** The specified reference has its data corrupted */ - GIT_EREFCORRUPTED = -16, - - /** The specified symbolic reference is too deeply nested */ + GIT_EREFCORRUPTED = -16, GIT_ETOONESTEDSYMREF = -17, - - /** The pack-refs file is either corrupted or its format is not currently supported */ GIT_EPACKEDREFSCORRUPTED = -18, - - /** The path is invalid */ GIT_EINVALIDPATH = -19, - - /** The revision walker is empty; there are no more commits left to iterate */ GIT_EREVWALKOVER = -20, - - /** The state of the reference is not valid */ GIT_EINVALIDREFSTATE = -21, - - /** This feature has not been implemented yet */ GIT_ENOTIMPLEMENTED = -22, - - /** A reference with this name already exists */ GIT_EEXISTS = -23, - - /** The given integer literal is too large to be parsed */ GIT_EOVERFLOW = -24, - - /** The given literal is not a valid number */ GIT_ENOTNUM = -25, - - /** Streaming error */ GIT_ESTREAM = -26, - - /** invalid arguments to function */ GIT_EINVALIDARGS = -27, - - /** The specified object has its data corrupted */ GIT_EOBJCORRUPTED = -28, + GIT_EAMBIGUOUS = -29, + GIT_EPASSTHROUGH = -30, + GIT_ENOMATCH = -31, + GIT_ESHORTBUFFER = -32, +}; +#endif - /** The given short oid is ambiguous */ - GIT_EAMBIGUOUSOIDPREFIX = -29, +/** Generic return codes */ +enum { + GIT_OK = 0, + GIT_ERROR = -1, + GIT_ENOTFOUND = -3, + GIT_EEXISTS = -4, + GIT_EAMBIGUOUS = -5, + GIT_EBUFS = -6, + + GIT_PASSTHROUGH = -30, + GIT_REVWALKOVER = -31, +}; + +typedef struct { + char *message; + int klass; } git_error; -/** - * Return a detailed error string with the latest error - * that occurred in the library. - * @return a string explaining the error - */ -GIT_EXTERN(const char *) git_lasterror(void); +typedef enum { + GITERR_NOMEMORY, + GITERR_OS, + GITERR_INVALID, + GITERR_REFERENCE, + GITERR_ZLIB, + GITERR_REPOSITORY, + GITERR_CONFIG, + GITERR_REGEX, + GITERR_ODB, + GITERR_INDEX, + GITERR_OBJECT, + GITERR_NET, + GITERR_TAG, + GITERR_TREE, + GITERR_INDEXER, + GITERR_SSL, +} git_error_t; /** - * strerror() for the Git library + * Return the last `git_error` object that was generated for the + * current thread or NULL if no error has occurred. * - * Get a string description for a given error code. - * NOTE: This method will be eventually deprecated in favor - * of the new `git_lasterror`. - * - * @param num The error code to explain - * @return a string explaining the error code + * @return A git_error object. */ -GIT_EXTERN(const char *) git_strerror(int num); +GIT_EXTERN(const git_error *) giterr_last(void); + +/** + * Clear the last library error that occurred for this thread. + */ +GIT_EXTERN(void) giterr_clear(void); /** @} */ GIT_END_DECL diff --git a/include/git2/index.h b/include/git2/index.h index fa1cadeab..0fb0f955a 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_index_h__ #define INCLUDE_git_index_h__ @@ -38,10 +20,10 @@ */ GIT_BEGIN_DECL -#define GIT_IDXENTRY_NAMEMASK (0x0fff) +#define GIT_IDXENTRY_NAMEMASK (0x0fff) #define GIT_IDXENTRY_STAGEMASK (0x3000) -#define GIT_IDXENTRY_EXTENDED (0x4000) -#define GIT_IDXENTRY_VALID (0x8000) +#define GIT_IDXENTRY_EXTENDED (0x4000) +#define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 /* @@ -51,26 +33,26 @@ GIT_BEGIN_DECL * * In-memory only flags: */ -#define GIT_IDXENTRY_UPDATE (1 << 0) -#define GIT_IDXENTRY_REMOVE (1 << 1) -#define GIT_IDXENTRY_UPTODATE (1 << 2) -#define GIT_IDXENTRY_ADDED (1 << 3) +#define GIT_IDXENTRY_UPDATE (1 << 0) +#define GIT_IDXENTRY_REMOVE (1 << 1) +#define GIT_IDXENTRY_UPTODATE (1 << 2) +#define GIT_IDXENTRY_ADDED (1 << 3) -#define GIT_IDXENTRY_HASHED (1 << 4) -#define GIT_IDXENTRY_UNHASHED (1 << 5) -#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ -#define GIT_IDXENTRY_CONFLICTED (1 << 7) +#define GIT_IDXENTRY_HASHED (1 << 4) +#define GIT_IDXENTRY_UNHASHED (1 << 5) +#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 7) -#define GIT_IDXENTRY_UNPACKED (1 << 8) +#define GIT_IDXENTRY_UNPACKED (1 << 8) #define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* * Extended on-disk flags: */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) /* GIT_IDXENTRY_EXTENDED2 is for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 15) +#define GIT_IDXENTRY_EXTENDED2 (1 << 15) #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) @@ -98,14 +80,14 @@ typedef struct git_index_entry { unsigned short flags; unsigned short flags_extended; - const char *path; + char *path; } git_index_entry; /** Representation of an unmerged file entry in the index. */ typedef struct git_index_entry_unmerged { unsigned int mode[3]; git_oid oid[3]; - const char *path; + char *path; } git_index_entry_unmerged; /** @@ -124,7 +106,7 @@ typedef struct git_index_entry_unmerged { * * @param index the pointer for the new index * @param index_path the path to the index file in disk - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path); @@ -149,7 +131,7 @@ GIT_EXTERN(void) git_index_free(git_index *index); * by reading from the hard disk. * * @param index an existing index object - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_read(git_index *index); @@ -158,7 +140,7 @@ GIT_EXTERN(int) git_index_read(git_index *index); * using an atomic file lock. * * @param index an existing index object - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_write(git_index *index); @@ -172,6 +154,13 @@ GIT_EXTERN(int) git_index_write(git_index *index); */ GIT_EXTERN(int) git_index_find(git_index *index, const char *path); +/** + * Remove all entries with equal path except last added + * + * @param index an existing index object + */ +GIT_EXTERN(void) git_index_uniq(git_index *index); + /** * Add or update an index entry from a file in disk * @@ -180,10 +169,14 @@ GIT_EXTERN(int) git_index_find(git_index *index, const char *path); * * This method will fail in bare index instances. * + * This forces the file to be added to the index, not looking + * at gitignore rules. Those rules can be evaluated through + * the git_status APIs (in status.h) before calling this. + * * @param index an existing index object * @param path filename to add * @param stage stage for the entry - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); @@ -195,7 +188,7 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); * * @param index an existing index object * @param source_entry new entry object - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry); @@ -214,7 +207,7 @@ GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_e * @param index an existing index object * @param path filename to add * @param stage stage for the entry - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); @@ -231,7 +224,7 @@ GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); * * @param index an existing index object * @param source_entry new entry object - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry); @@ -240,7 +233,7 @@ GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *sourc * * @param index an existing index object * @param position position of the entry to remove - * @return 0 on success, otherwise an error code + * @return 0 or an error code */ GIT_EXTERN(int) git_index_remove(git_index *index, int position); @@ -257,7 +250,7 @@ GIT_EXTERN(int) git_index_remove(git_index *index, int position); * @param n the position of the entry * @return a pointer to the entry; NULL if out of bounds */ -GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, int n); +GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, unsigned int n); /** * Get the count of entries currently in the index @@ -285,12 +278,24 @@ GIT_EXTERN(unsigned int) git_index_entrycount_unmerged(git_index *index); * @param path path to search * @return the unmerged entry; NULL if not found */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged(git_index *index, const char *path); +GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path); + +/** + * Get an unmerged entry from the index. + * + * The returned entry is read-only and should not be modified + * of freed by the caller. + * + * @param index an existing index object + * @param n the position of the entry + * @return a pointer to the unmerged entry; NULL if out of bounds + */ +GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, unsigned int n); /** * Return the stage number from a git index entry * - * This entry is calculated from the entrie's flag + * This entry is calculated from the entry's flag * attribute like this: * * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT @@ -300,6 +305,17 @@ GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged(git_index *i */ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); +/** + * Read a tree into the index file + * + * The current index contents will be replaced by the specified tree. + * + * @param index an existing index object + * @param tree tree to read + * @return 0 or an error code + */ +GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/indexer.h b/include/git2/indexer.h new file mode 100644 index 000000000..626377701 --- /dev/null +++ b/include/git2/indexer.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_indexer_h__ +#define _INCLUDE_git_indexer_h__ + +#include "common.h" +#include "oid.h" + +GIT_BEGIN_DECL + +/** + * This is passed as the first argument to the callback to allow the + * user to see the progress. + */ +typedef struct git_indexer_stats { + unsigned int total; + unsigned int processed; +} git_indexer_stats; + + +typedef struct git_indexer git_indexer; +typedef struct git_indexer_stream git_indexer_stream; + +/** + * Create a new streaming indexer instance + * + * @param out where to store the indexer instance + * @param path to the gitdir (metadata directory) + */ +GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *gitdir); + +/** + * Add data to the indexer + * + * @param idx the indexer + * @param data the data to add + * @param size the size of the data + * @param stats stat storage + */ +GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats); + +/** + * Finalize the pack and index + * + * Resolve any pending deltas and write out the index file + * + * @param idx the indexer + */ +GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats); + +/** + * 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 index has been finalized. + * + * @param idx the indexer instance + */ +GIT_EXTERN(const git_oid *) git_indexer_stream_hash(git_indexer_stream *idx); + +/** + * Free the indexer and its resources + * + * @param idx the indexer to free + */ +GIT_EXTERN(void) git_indexer_stream_free(git_indexer_stream *idx); + +/** + * Create a new indexer instance + * + * @param out where to store the indexer instance + * @param packname the absolute filename of the packfile to index + */ +GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname); + +/** + * Iterate over the objects in the packfile and extract the information + * + * Indexing a packfile can be very expensive so this function is + * expected to be run in a worker thread and the stats used to provide + * feedback the user. + * + * @param idx the indexer instance + * @param stats storage for the running state + */ +GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats); + +/** + * Write the index file to disk. + * + * The file will be stored as pack-$hash.idx in the same directory as + * the packfile. + * + * @param idx the indexer instance + */ +GIT_EXTERN(int) git_indexer_write(git_indexer *idx); + +/** + * 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 index has been written to disk. + * + * @param idx the indexer instance + */ +GIT_EXTERN(const git_oid *) git_indexer_hash(git_indexer *idx); + +/** + * Free the indexer and its resources + * + * @param idx the indexer to free + */ +GIT_EXTERN(void) git_indexer_free(git_indexer *idx); + +GIT_END_DECL + +#endif diff --git a/include/git2/inttypes.h b/include/git2/inttypes.h new file mode 100644 index 000000000..ead903f78 --- /dev/null +++ b/include/git2/inttypes.h @@ -0,0 +1,305 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are 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 of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + + +#endif // _MSC_INTTYPES_H_ ] diff --git a/include/git2/merge.h b/include/git2/merge.h new file mode 100644 index 000000000..5a0b2e7f2 --- /dev/null +++ b/include/git2/merge.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_merge_h__ +#define INCLUDE_git_merge_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" + +/** + * @file git2/merge.h + * @brief Git merge-base routines + * @defgroup git_revwalk Git merge-base routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Find a merge base between two commits + * + * @param out the OID of a merge base between 'one' and 'two' + * @param repo the repository where the commits exist + * @param one one of the commits + * @param two the other commit + */ +GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/net.h b/include/git2/net.h new file mode 100644 index 000000000..c2301b6f1 --- /dev/null +++ b/include/git2/net.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_net_h__ +#define INCLUDE_net_h__ + +#include "common.h" +#include "oid.h" +#include "types.h" + +/** + * @file git2/net.h + * @brief Git networking declarations + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +#define GIT_DEFAULT_PORT "9418" + +/* + * We need this because we need to know whether we should call + * git-upload-pack or git-receive-pack on the remote end when get_refs + * gets called. + */ + +#define GIT_DIR_FETCH 0 +#define GIT_DIR_PUSH 1 + + +/** + * Remote head description, given out on `ls` calls. + */ +struct git_remote_head { + int local:1; /* available locally */ + git_oid oid; + git_oid loid; + char *name; +}; + +/** + * Callback for listing the remote heads + */ +typedef int (*git_headlist_cb)(git_remote_head *, void *); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/notes.h b/include/git2/notes.h new file mode 100644 index 000000000..19073abd1 --- /dev/null +++ b/include/git2/notes.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_note_h__ +#define INCLUDE_git_note_h__ + +#include "oid.h" + +/** + * @file git2/notes.h + * @brief Git notes management routines + * @defgroup git_note Git notes management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Read the note for an object + * + * The note must be freed manually by the user. + * + * @param note the note; NULL in case of error + * @param repo the Git repository + * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" + * @param oid OID of the object + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_read(git_note **note, git_repository *repo, + const char *notes_ref, const git_oid *oid); + +/** + * Get the note message + * + * @param note + * @return the note message + */ +GIT_EXTERN(const char *) git_note_message(git_note *note); + + +/** + * Get the note object OID + * + * @param note + * @return the note object OID + */ +GIT_EXTERN(const git_oid *) git_note_oid(git_note *note); + + +/** + * Add a note for an object + * + * @param oid pointer to store the OID (optional); NULL in case of error + * @param repo the Git repository + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param notes_ref OID reference to update (optional); defaults to "refs/notes/commits" + * @param oid The OID of the object + * @param oid The note to add for object oid + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_create(git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *oid, + const char *note); + + +/** + * Remove the note for an object + * + * @param repo the Git repository + * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" + * @param author signature of the notes commit author + * @param committer signature of the notes commit committer + * @param oid the oid which note's to be removed + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_remove(git_repository *repo, const char *notes_ref, + git_signature *author, git_signature *committer, + const git_oid *oid); + +/** + * Free a git_note object + * + * @param note git_note object + */ +GIT_EXTERN(void) git_note_free(git_note *note); + +/** + * Get the default notes reference for a repository + * + * @param out Pointer to the default notes reference + * @param repo The Git repository + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_note_default_ref(const char **out, git_repository *repo); + +/** + * Basic components of a note + * + * - Oid of the blob containing the message + * - Oid of the git object being annotated + */ +typedef struct { + git_oid blob_oid; + git_oid annotated_object_oid; +} git_note_data; + +/** + * Loop over all the notes within a specified namespace + * and issue a callback for each one. + * + * @param repo Repository where to find the notes. + * + * @param notes_ref OID reference to read from (optional); defaults to "refs/notes/commits". + * + * @param note_cb Callback to invoke per found annotation. + * + * @param payload Extra parameter to callback function. + * + * @return 0 or an error code. + */ +GIT_EXTERN(int) git_note_foreach( + git_repository *repo, + const char *notes_ref, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload +); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/object.h b/include/git2/object.h index 83d37a263..414325121 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_object_h__ #define INCLUDE_git_object_h__ @@ -39,10 +21,10 @@ GIT_BEGIN_DECL /** - * Lookup a reference to one of the objects in a repostory. + * Lookup a reference to one of the objects in a repository. * * The generated reference is owned by the repository and - * should be closed with the `git_object_close` method + * should be closed with the `git_object_free` method * instead of free'd manually. * * The 'type' parameter must match the type of the object @@ -63,7 +45,7 @@ GIT_EXTERN(int) git_object_lookup( git_otype type); /** - * Lookup a reference to one of the objects in a repostory, + * Lookup a reference to one of the objects in a repository, * given a prefix of its identifier (short id). * * The object obtained will be so that its identifier @@ -74,7 +56,7 @@ GIT_EXTERN(int) git_object_lookup( * the prefix; otherwise the method will fail. * * The generated reference is owned by the repository and - * should be closed with the `git_object_close` method + * should be closed with the `git_object_free` method * instead of free'd manually. * * The 'type' parameter must match the type of the object @@ -82,12 +64,12 @@ GIT_EXTERN(int) git_object_lookup( * The special value 'GIT_OBJ_ANY' may be passed to let * the method guess the object's type. * - * @param object pointer to the looked-up object + * @param object_out pointer where to store the looked-up object * @param repo the repository to look up the object * @param id a short identifier for the object * @param len the length of the short identifier * @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_prefix( git_object **object_out, @@ -132,7 +114,7 @@ GIT_EXTERN(git_repository *) git_object_owner(const git_object *obj); * This method instructs the library to close an existing * object; note that git_objects are owned and cached by the repository * so the object may or may not be freed after this library call, - * depending on how agressive is the caching mechanism used + * depending on how aggressive is the caching mechanism used * by the repository. * * IMPORTANT: @@ -141,7 +123,7 @@ GIT_EXTERN(git_repository *) git_object_owner(const git_object *obj); * * @param object the object to close */ -GIT_EXTERN(void) git_object_close(git_object *object); +GIT_EXTERN(void) git_object_free(git_object *object); /** * Convert an object type to it's string representation. diff --git a/include/git2/odb.h b/include/git2/odb.h index 42961463c..e2443178c 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_odb_h__ #define INCLUDE_git_odb_h__ @@ -46,9 +28,8 @@ GIT_BEGIN_DECL * backend must be manually added using `git_odb_add_backend()` * * @param out location to store the database pointer, if opened. - * Set to NULL if the open failed. - * @return GIT_SUCCESS if the database was created; otherwise an error - * code describing why the open was not possible. + * Set to NULL if the open failed. + * @return 0 or an error code */ GIT_EXTERN(int) git_odb_new(git_odb **out); @@ -64,21 +45,24 @@ GIT_EXTERN(int) git_odb_new(git_odb **out); * contains a 'pack/' folder with the corresponding data * * @param out location to store the database pointer, if opened. - * Set to NULL if the open failed. + * Set to NULL if the open failed. * @param objects_dir path of the backends' "objects" directory. - * @return GIT_SUCCESS if the database opened; otherwise an error - * code describing why the open was not possible. + * @return 0 or an error code */ GIT_EXTERN(int) git_odb_open(git_odb **out, const char *objects_dir); /** * Add a custom backend to an existing Object DB * + * The backends are checked in relative ordering, based on the + * value of the `priority` parameter. + * * Read for more information. * * @param odb database to add the backend to - * @paramm backend pointer to a git_odb_backend instance - * @return 0 on sucess; error code otherwise + * @param backend pointer to a git_odb_backend instance + * @param priority Value for ordering the backends queue + * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority); @@ -89,22 +73,26 @@ GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int * Alternate backends are always checked for objects *after* * all the main backends have been exhausted. * + * The backends are checked in relative ordering, based on the + * value of the `priority` parameter. + * * Writing is disabled on alternate backends. * * Read for more information. * * @param odb database to add the backend to - * @paramm backend pointer to a git_odb_backend instance - * @return 0 on sucess; error code otherwise + * @param backend pointer to a git_odb_backend instance + * @param priority Value for ordering the backends queue + * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority); /** * Close an open object database. * - * @param db database pointer to close. If NULL no action is taken. + * @param db database pointer to close. If NULL no action is taken. */ -GIT_EXTERN(void) git_odb_close(git_odb *db); +GIT_EXTERN(void) git_odb_free(git_odb *db); /** * Read an object from the database. @@ -120,7 +108,7 @@ GIT_EXTERN(void) git_odb_close(git_odb *db); * @param db database to search for the object in. * @param id identity of the object to read. * @return - * - GIT_SUCCESS if the object was read; + * - 0 if the object was read; * - GIT_ENOTFOUND if the object is not in the database. */ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id); @@ -143,16 +131,13 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * internally cached, so it should be closed * by the user once it's no longer in use. * - * @param out_oid the oid of the unique object matching - * the short id * @param out pointer where to store the read object * @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 - * - GIT_SUCCESS 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) + * @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) */ GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len); @@ -171,7 +156,7 @@ GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git * @param db database to search for the object in. * @param id identity of the object to read. * @return - * - GIT_SUCCESS if the object was read; + * - 0 if the object was read; * - GIT_ENOTFOUND if the object is not in the database. */ GIT_EXTERN(int) git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id); @@ -200,10 +185,10 @@ GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id); * * @param oid pointer to store the OID result of the write * @param odb object database where to store the object - * @param data buffer with the data to storr + * @param data buffer with the data to store * @param len size of the buffer * @param type type of the data to store - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_odb_write(git_oid *oid, git_odb *odb, const void *data, size_t len, git_otype type); @@ -215,12 +200,12 @@ GIT_EXTERN(int) git_odb_write(git_oid *oid, git_odb *odb, const void *data, size * * The returned stream will be of type `GIT_STREAM_WRONLY` and * will have the following methods: - * + * * - 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 * @@ -247,7 +232,7 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_ * * The returned stream will be of type `GIT_STREAM_RDONLY` and * will have the following methods: - * + * * - stream->read: read `n` bytes from the stream * - stream->free: free the stream * @@ -265,17 +250,30 @@ GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const /** * Determine the object-ID (sha1 hash) of a data buffer * - * The resulting SHA-1 OID will the itentifier for the data + * The resulting SHA-1 OID will be the identifier for the data * buffer as if the data buffer it were to written to the ODB. * * @param id the resulting object-ID. * @param data data to hash * @param len size of the data * @param type of the data to hash - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type); +/** + * Read a file from disk and fill a git_oid with the object id + * that the file would have if it were written to the Object + * Database as an object of the given type. Similar functionality + * to git.git's `git hash-object` without the `-w` flag. + * + * @param out oid structure the result is written into. + * @param path file to read and determine object id for + * @param type the type of the object that will be hashed + * @return 0 or an error code + */ +GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type); + /** * Close an ODB object * @@ -284,7 +282,7 @@ GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otyp * * @param object object to close */ -GIT_EXTERN(void) git_odb_object_close(git_odb_object *object); +GIT_EXTERN(void) git_odb_object_free(git_odb_object *object); /** * Return the OID of an ODB object diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 796d2b9da..f4620f5f4 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_odb_backend_h__ #define INCLUDE_git_odb_backend_h__ @@ -92,6 +74,13 @@ struct git_odb_backend { void (* free)(struct git_odb_backend *); }; +/** Streaming mode */ +enum { + GIT_STREAM_RDONLY = (1 << 1), + GIT_STREAM_WRONLY = (1 << 2), + GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), +}; + /** A stream to read/write from a backend */ struct git_odb_stream { struct git_odb_backend *backend; @@ -103,17 +92,8 @@ struct git_odb_stream { void (*free)(struct git_odb_stream *stream); }; -/** Streaming mode */ -typedef enum { - GIT_STREAM_RDONLY = (1 << 1), - GIT_STREAM_WRONLY = (1 << 2), - GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY), -} git_odb_streammode; - - GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir); -GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir); -GIT_EXTERN(int) git_odb_backend_sqlite(git_odb_backend **backend_out, const char *sqlite_db); +GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir, int compression_level, int do_fsync); GIT_END_DECL diff --git a/include/git2/oid.h b/include/git2/oid.h index f1f7dcc6c..a05b40a37 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_oid_h__ #define INCLUDE_git_oid_h__ @@ -48,65 +30,83 @@ GIT_BEGIN_DECL #define GIT_OID_MINPREFIXLEN 4 /** Unique identity of any object (commit, tree, blob, tag). */ -typedef struct { +typedef struct _git_oid git_oid; +struct _git_oid { /** raw binary formatted id */ unsigned char id[GIT_OID_RAWSZ]; -} git_oid; +}; /** * Parse a hex formatted object id into a git_oid. + * * @param out oid structure the result is written into. * @param str input hex string; must be pointing at the start of - * the hex sequence and have at least the number of bytes - * needed for an oid encoded in hex (40 bytes). - * @return GIT_SUCCESS if valid; GIT_ENOTOID on failure. + * the hex sequence and have at least the number of bytes + * needed for an oid encoded in hex (40 bytes). + * @return 0 or an error code */ -GIT_EXTERN(int) git_oid_mkstr(git_oid *out, const char *str); +GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str); + +/** + * Parse N characters of a hex formatted object id into a git_oid + * + * If N is odd, N-1 characters will be parsed instead. + * The remaining space in the git_oid will be set to zero. + * + * @param out oid structure the result is written into. + * @param str input hex string of at least size `length` + * @param length length of the input string + * @return 0 or an error code + */ +GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length); /** * Copy an already raw oid into a git_oid structure. + * * @param out oid structure the result is written into. * @param raw the raw input bytes to be copied. */ -GIT_EXTERN(void) git_oid_mkraw(git_oid *out, const unsigned char *raw); +GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw); /** * Format a git_oid into a hex string. + * * @param str output hex string; must be pointing at the start of - * the hex sequence and have at least the number of bytes - * needed for an oid encoded in hex (40 bytes). Only the - * oid digits are written; a '\\0' terminator must be added - * by the caller if it is required. + * the hex sequence and have at least the number of bytes + * needed for an oid encoded in hex (40 bytes). Only the + * oid digits are written; a '\\0' terminator must be added + * by the caller if it is required. * @param oid oid structure to format. */ GIT_EXTERN(void) git_oid_fmt(char *str, const git_oid *oid); /** * Format a git_oid into a loose-object path string. - *

+ * * The resulting string is "aa/...", where "aa" is the first two - * hex digitis of the oid and "..." is the remaining 38 digits. + * hex digits of the oid and "..." is the remaining 38 digits. * * @param str output hex string; must be pointing at the start of - * the hex sequence and have at least the number of bytes - * needed for an oid encoded in hex (41 bytes). Only the - * oid digits are written; a '\\0' terminator must be added - * by the caller if it is required. + * the hex sequence and have at least the number of bytes + * needed for an oid encoded in hex (41 bytes). Only the + * oid digits are written; a '\\0' terminator must be added + * by the caller if it is required. * @param oid oid structure to format. */ GIT_EXTERN(void) git_oid_pathfmt(char *str, const git_oid *oid); /** - * Format a gid_oid into a newly allocated c-string. + * Format a git_oid into a newly allocated c-string. + * * @param oid the oid structure to format - * @return the c-string; NULL if memory is exhausted. Caller must - * deallocate the string with free(). + * @return the c-string; NULL if memory is exhausted. Caller must + * deallocate the string with git__free(). */ GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *oid); /** * Format a git_oid into a buffer as a hex format c-string. - *

+ * * If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting * oid c-string will be truncated to n-1 characters. If there are * any input parameter errors (out == NULL, n == 0, oid == NULL), @@ -117,12 +117,13 @@ GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *oid); * @param n the size of the out buffer. * @param oid the oid structure to format. * @return the out buffer pointer, assuming no input parameter - * errors, otherwise a pointer to an empty string. + * errors, otherwise a pointer to an empty string. */ -GIT_EXTERN(char *) git_oid_to_string(char *out, size_t n, const git_oid *oid); +GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *oid); /** * Copy an oid from one structure to another. + * * @param out oid structure the result is written into. * @param src oid structure to copy from. */ @@ -130,6 +131,7 @@ GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src); /** * 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. @@ -139,12 +141,28 @@ GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b); /** * Compare the first 'len' hexadecimal characters (packets of 4 bits) * of two oid structures. - * @param len the number of hex chars to compare + * * @param a first oid structure. * @param b second oid structure. + * @param len the number of hex chars to compare * @return 0 in case of a match */ -GIT_EXTERN(int) gid_oid_match(unsigned int len, git_oid *a, git_oid *b); +GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int len); + +/** + * Check if an oid equals an hex formatted object id. + * + * @param a 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. + */ +GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str); + +/** + * Check is an oid is all zeros. + */ +GIT_EXTERN(int) git_oid_iszero(const git_oid *a); /** * OID Shortener object @@ -165,7 +183,7 @@ typedef struct git_oid_shorten git_oid_shorten; * be unique. * @return a `git_oid_shorten` instance, NULL if OOM */ -git_oid_shorten *git_oid_shorten_new(size_t min_length); +GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length); /** * Add a new OID to set of shortened OIDs and calculate @@ -191,14 +209,14 @@ git_oid_shorten *git_oid_shorten_new(size_t min_length); * added so far to the set; or an error code (<0) if an * error occurs. */ -int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid); +GIT_EXTERN(int) git_oid_shorten_add(git_oid_shorten *os, const char *text_oid); /** * Free an OID shortener instance - * + * * @param os a `git_oid_shorten` instance */ -void git_oid_shorten_free(git_oid_shorten *os); +GIT_EXTERN(void) git_oid_shorten_free(git_oid_shorten *os); /** @} */ GIT_END_DECL diff --git a/include/git2/reflog.h b/include/git2/reflog.h new file mode 100644 index 000000000..f490e29de --- /dev/null +++ b/include/git2/reflog.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_reflog_h__ +#define INCLUDE_git_reflog_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" + +/** + * @file git2/reflog.h + * @brief Git reflog management routines + * @defgroup git_reflog Git reflog management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Read the reflog for the given reference + * + * The reflog must be freed manually by using + * git_reflog_free(). + * + * @param reflog pointer to reflog + * @param ref reference to read the reflog for + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); + +/** + * Write a new reflog for the given reference + * + * If there is no reflog file for the given + * reference yet, it will be created. + * + * `oid_old` may be NULL in case it's a new reference. + * + * `msg` is optional and can be NULL. + * + * @param ref the changed reference + * @param oid_old the OID the reference was 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_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); + +/** + * Rename the reflog for the given reference + * + * @param ref the reference + * @param new_name the new name of the reference + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *new_name); + +/** + * Delete the reflog for the given reference + * + * @param ref the reference + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_delete(git_reference *ref); + +/** + * Get the number of log entries in a reflog + * + * @param reflog the previously loaded reflog + * @return the number of log entries + */ +GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog); + +/** + * Lookup an entry by its index + * + * @param reflog a previously loaded reflog + * @param idx the position to lookup + * @return the entry; NULL if not found + */ +GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx); + +/** + * Get the old oid + * + * @param entry a reflog entry + * @return the old oid + */ +GIT_EXTERN(const git_oid *) git_reflog_entry_oidold(const git_reflog_entry *entry); + +/** + * Get the new oid + * + * @param entry a reflog entry + * @return the new oid at this time + */ +GIT_EXTERN(const git_oid *) git_reflog_entry_oidnew(const git_reflog_entry *entry); + +/** + * Get the committer of this entry + * + * @param entry a reflog entry + * @return the committer + */ +GIT_EXTERN(git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry); + +/** + * Get the log msg + * + * @param entry a reflog entry + * @return the log msg + */ +GIT_EXTERN(char *) git_reflog_entry_msg(const git_reflog_entry *entry); + +/** + * Free the reflog + * + * @param reflog reflog to free + */ +GIT_EXTERN(void) git_reflog_free(git_reflog *reflog); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/refs.h b/include/git2/refs.h index 298c66d51..2918215aa 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_refs_h__ #define INCLUDE_git_refs_h__ @@ -41,53 +23,45 @@ GIT_BEGIN_DECL /** * Lookup a reference by its name in a repository. * - * The generated reference is owned by the repository and - * should not be freed by the user. + * The generated reference must be freed by the user. * * @param reference_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, ref/heads/master, refs/tags/v0.1.0, ...) - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name); +/** + * Lookup a reference by name and resolve immediately to OID. + * + * @param oid 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 + * @return 0 on success, -1 if name could not be resolved + */ +GIT_EXTERN(int) git_reference_name_to_oid( + git_oid *out, git_repository *repo, const char *name); + /** * Create a new symbolic reference. * * The reference will be created in the repository and written * to the disk. * - * This reference is owned by the repository and shall not - * be free'd by the user. + * The generated reference must be freed by the user. + * + * If `force` is true and there already exists a reference + * with the same name, it will be overwritten. * * @param ref_out Pointer to the newly created reference * @param repo Repository where that reference will live * @param name The name of the reference * @param target The target of the reference - * @return 0 on success; error code otherwise + * @param force Overwrite existing references + * @return 0 or an error code */ -GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target); - -/** - * Create a new symbolic reference, overwriting an existing one with - * the same name, if it exists. - * - * If the new reference isn't a symbolic one, any pointers to the old - * reference become invalid. - * - * The reference will be created in the repository and written - * to the disk. - * - * This reference is owned by the repository and shall not - * be free'd by the user. - * - * @param ref_out Pointer to the newly created reference - * @param repo Repository where that reference will live - * @param name The name of the reference - * @param target The target of the reference - * @return 0 on success; error code otherwise - */ -GIT_EXTERN(int) git_reference_create_symbolic_f(git_reference **ref_out, git_repository *repo, const char *name, const char *target); +GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force); /** * Create a new object id reference. @@ -95,41 +69,23 @@ GIT_EXTERN(int) git_reference_create_symbolic_f(git_reference **ref_out, git_rep * The reference will be created in the repository and written * to the disk. * - * This reference is owned by the repository and shall not - * be free'd by the user. + * The generated reference must be freed by the user. + * + * If `force` is true and there already exists a reference + * with the same name, it will be overwritten. * * @param ref_out Pointer to the newly created reference * @param repo Repository where that reference will live * @param name The name of the reference * @param id The object id pointed to by the reference. - * @return 0 on success; error code otherwise + * @param force Overwrite existing references + * @return 0 or an error code */ -GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id); - -/** - * Create a new object id reference, overwriting an existing one with - * the same name, if it exists. - * - * If the new reference isn't an object id one, any pointers to the - * old reference become invalid. - * - * The reference will be created in the repository and written - * to the disk. - * - * This reference is owned by the repository and shall not - * be free'd by the user. - * - * @param ref_out Pointer to the newly created reference - * @param repo Repository where that reference will live - * @param name The name of the reference - * @param id The object id pointed to by the reference. - * @return 0 on success; error code otherwise - */ -GIT_EXTERN(int) git_reference_create_oid_f(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id); +GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force); /** * Get the OID pointed to by a reference. - * + * * Only available if the reference is direct (i.e. not symbolic) * * @param ref The reference @@ -139,7 +95,7 @@ GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref); /** * Get full name to the reference pointed by this reference - * + * * Only available if the reference is symbolic * * @param ref The reference @@ -155,7 +111,7 @@ GIT_EXTERN(const char *) git_reference_target(git_reference *ref); * @param ref The reference * @return the type */ -GIT_EXTERN(git_rtype) git_reference_type(git_reference *ref); +GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref); /** * Get the full name of a reference @@ -166,17 +122,22 @@ GIT_EXTERN(git_rtype) git_reference_type(git_reference *ref); GIT_EXTERN(const char *) git_reference_name(git_reference *ref); /** - * Resolve a symbolic reference + * Resolve a symbolic reference * - * Thie method iteratively peels a symbolic reference + * This method iteratively peels a symbolic reference * until it resolves to a direct reference to an OID. * + * The peeled reference is returned in the `resolved_ref` + * argument, and must be freed manually once it's no longer + * needed. + * * If a direct reference is passed as an argument, - * that reference is returned immediately + * a copy of that reference is returned. This copy must + * be manually freed too. * * @param resolved_ref Pointer to the peeled reference * @param ref The reference - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref); @@ -199,7 +160,7 @@ GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref); * * @param ref The reference * @param target The new target for the reference - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target); @@ -213,8 +174,8 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target) * memory and on disk. * * @param ref The reference - * @param target The new target OID for the reference - * @return 0 on success; error code otherwise + * @param id The new target OID for the reference + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); @@ -225,25 +186,26 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); * The new name will be checked for validity and may be * modified into a normalized form. * - * The refernece will be immediately renamed in-memory + * The given git_reference will be updated in place. + * + * The reference will be immediately renamed in-memory * and on disk. * - */ -GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name); - -/** - * Rename an existing reference, overwriting an existing one with the - * same name, if it exists. + * If the `force` flag is not enabled, and there's already + * a reference with the given name, the renaming will fail. * - * This method works for both direct and symbolic references. - * The new name will be checked for validity and may be - * modified into a normalized form. + * IMPORTANT: + * The user needs to write a proper reflog entry if the + * reflog is enabled for the repository. We only rename + * the reflog if it exists. * - * The refernece will be immediately renamed in-memory - * and on disk. + * @param ref The reference to rename + * @param new_name The new name for the reference + * @param force Overwrite an existing reference + * @return 0 or an error code * */ -GIT_EXTERN(int) git_reference_rename_f(git_reference *ref, const char *new_name); +GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force); /** * Delete an existing reference @@ -253,6 +215,8 @@ GIT_EXTERN(int) git_reference_rename_f(git_reference *ref, const char *new_name) * The reference will be immediately removed on disk and from * memory. The given reference pointer will no longer be valid. * + * @param ref The reference to remove + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_delete(git_reference *ref); @@ -260,17 +224,14 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); * Pack all the loose references in the repository * * This method will load into the cache all the loose - * references on the repository and update the + * references on the repository and update the * `packed-refs` file with them. * * Once the `packed-refs` file has been written properly, * the loose references will be removed from disk. * - * WARNING: calling this method may invalidate any existing - * references previously loaded on the cache. - * * @param repo Repository where the loose refs will be packed - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_reference_packall(git_repository *repo); @@ -293,16 +254,15 @@ GIT_EXTERN(int) git_reference_packall(git_repository *repo); * @param repo Repository where to find the refs * @param list_flags Filtering flags for the reference * listing. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags); +GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, unsigned int list_flags); /** - * List all the references in the repository, calling a custom - * callback for each one. + * Perform an operation on each reference in the repository * - * The listed references may be filtered by type, or using + * The processed references may be filtered by type, or using * a bitwise OR of several types. Use the magic value * `GIT_REF_LISTALL` to obtain all references, including * packed ones. @@ -316,9 +276,53 @@ GIT_EXTERN(int) git_reference_listall(git_strarray *array, git_repository *repo, * listing. * @param callback Function which will be called for every listed ref * @param payload Additional data to pass to the callback - * @return 0 on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload); +GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload); + +/** + * Check if a reference has been loaded from a packfile + * + * @param ref A git reference + * @return 0 in case it's not packed; 1 otherwise + */ +GIT_EXTERN(int) git_reference_is_packed(git_reference *ref); + +/** + * Reload a reference from disk + * + * Reference pointers may become outdated if the Git + * repository is accessed simultaneously by other clients + * while the library is open. + * + * This method forces a reload of the reference from disk, + * to ensure that the provided information is still + * reliable. + * + * If the reload fails (e.g. the reference no longer exists + * on disk, or has become corrupted), an error code will be + * returned and the reference pointer will be invalidated. + * + * @param ref The reference to reload + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_reference_reload(git_reference *ref); + +/** + * Free the given reference + * + * @param ref git_reference + */ +GIT_EXTERN(void) git_reference_free(git_reference *ref); + +/** + * Compare two references. + * + * @param ref1 The first git_reference + * @param ref2 The second git_reference + * @return 0 if the same, else a stable but meaningless ordering. + */ +GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2); /** @} */ GIT_END_DECL diff --git a/include/git2/refspec.h b/include/git2/refspec.h new file mode 100644 index 000000000..1100e9022 --- /dev/null +++ b/include/git2/refspec.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_refspec_h__ +#define INCLUDE_git_refspec_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/refspec.h + * @brief Git refspec attributes + * @defgroup git_refspec Git refspec attributes + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Get the source specifier + * + * @param refspec the refspec + * @return the refspec's source specifier + */ +GIT_EXTERN(const char *) git_refspec_src(const git_refspec *refspec); + +/** + * Get the destination specifier + * + * @param refspec the refspec + * @return the refspec's destination specifier + */ +GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec); + +/** + * Get the force update setting + * + * @param refspec the refspec + * @return 1 if force update has been set, 0 otherwise + */ +GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec); + +/** + * Check if a refspec's source descriptor matches a reference + * + * @param refspec the refspec + * @param refname the name of the reference to check + * @return 1 if the refspec matches, 0 otherwise + */ +GIT_EXTERN(int) git_refspec_src_matches(const git_refspec *refspec, const char *refname); + +/** + * Transform a reference to its target following the refspec's rules + * + * @param out where to store the target name + * @param outlen the size ouf the `out` buffer + * @param spec the refspec + * @param name the name of the reference to transform + * @return 0, GIT_EBUFS or another error + */ +GIT_EXTERN(int) git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name); + +GIT_END_DECL + +#endif diff --git a/include/git2/remote.h b/include/git2/remote.h new file mode 100644 index 000000000..5c01949d2 --- /dev/null +++ b/include/git2/remote.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_remote_h__ +#define INCLUDE_git_remote_h__ + +#include "common.h" +#include "repository.h" +#include "refspec.h" +#include "net.h" +#include "indexer.h" + +/** + * @file git2/remote.h + * @brief Git remote management functions + * @defgroup git_remote remote management functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/* + * TODO: This functions still need to be implemented: + * - _listcb/_foreach + * - _add + * - _rename + * - _del (needs support from config) + */ + +/** + * Create a remote in memory + * + * Create a remote with the default refspecs in memory. You can use + * this when you have a URL instead of a remote's name. + * + * @param out pointer to the new remote object + * @param repo the associated repository + * @param name the remote's name + * @param url the remote repository's URL + * @param fetch the fetch refspec to use for this remote + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch); + +/** + * Get the information for a particular remote + * + * @param out pointer to the new remote object + * @param cfg the repository's configuration + * @param name the remote's name + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const char *name); + +/** + * Save a remote to its repository's configuration + * + * @param remote the remote to save to config + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_save(const git_remote *remote); + +/** + * Get the remote's name + * + * @param remote the remote + * @return a pointer to the name + */ +GIT_EXTERN(const char *) git_remote_name(git_remote *remote); + +/** + * Get the remote's url + * + * @param remote the remote + * @return a pointer to the url + */ +GIT_EXTERN(const char *) git_remote_url(git_remote *remote); + +/** + * Set the remote's fetch refspec + * + * @param remote the remote + * @apram spec the new fetch refspec + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_fetchspec(git_remote *remote, const char *spec); + +/** + * Get the fetch refspec + * + * @param remote the remote + * @return a pointer to the fetch refspec or NULL if it doesn't exist + */ +GIT_EXTERN(const git_refspec *) git_remote_fetchspec(git_remote *remote); + +/** + * Set the remote's push refspec + * + * @param remote the remote + * @param spec the new push refspec + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_pushspec(git_remote *remote, const char *spec); + +/** + * Get the push refspec + * + * @param remote the remote + * @return a pointer to the push refspec or NULL if it doesn't exist + */ + +GIT_EXTERN(const git_refspec *) git_remote_pushspec(git_remote *remote); + +/** + * Open a connection to a remote + * + * The transport is selected based on the URL. The direction argument + * is due to a limitation of the git protocol (over TCP or SSH) which + * starts up a specific binary which can only do the one or the other. + * + * @param remote the remote to connect to + * @param direction whether you want to receive or send data + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_connect(git_remote *remote, int direction); + +/** + * Get a list of refs at the remote + * + * The remote (or more exactly its transport) must be connected. The + * memory belongs to the remote. + * + * @param refs where to store the refs + * @param remote the remote + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); + +/** + * Download 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. + * + * @param remote the remote to download from + * @param filename where to store the temporary filename + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); + +/** + * Check whether the remote is connected + * + * Check whether the remote's underlying transport is connected to the + * remote host. + * + * @return 1 if it's connected, 0 otherwise. + */ +GIT_EXTERN(int) git_remote_connected(git_remote *remote); + +/** + * Disconnect from the remote + * + * Close the connection to the remote and free the underlying + * transport. + * + * @param remote the remote to disconnect from + */ +GIT_EXTERN(void) git_remote_disconnect(git_remote *remote); + +/** + * Free the memory associated with a remote + * + * This also disconnects from the remote, if the connection + * has not been closed yet (using git_remote_disconnect). + * + * @param remote the remote to free + */ +GIT_EXTERN(void) git_remote_free(git_remote *remote); + +/** + * Update the tips to the new state + * + * @param remote the remote to update + * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value + */ +GIT_EXTERN(int) git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)); + +/** + * Return whether a string is a valid remote URL + * + * @param url the url to check + * @param 1 if the url is valid, 0 otherwise + */ +GIT_EXTERN(int) git_remote_valid_url(const char *url); + +/** + * Return whether the passed URL is supported by this version of the library. + * + * @param url the url to check + * @return 1 if the url is supported, 0 otherwise +*/ +GIT_EXTERN(int) git_remote_supported_url(const char* url); + +/** + * Get a list of the configured remotes for a repo + * + * The string array must be freed by the user. + * + * @param remotes_list a string array with the names of the remotes + * @param repo the repository to query + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo); + +/** + * Add a remote with the default fetch refspec to the repository's configuration + * + * @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 + */ +GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url); + +/** + * Choose whether to check the server's certificate (applies to HTTPS only) + * + * @param remote the remote to configure + * @param check whether to check the server's certificate (defaults to yes) + */ + +GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/repository.h b/include/git2/repository.h index 493e82ad5..0b56a0870 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_repository_h__ #define INCLUDE_git_repository_h__ @@ -41,131 +23,73 @@ GIT_BEGIN_DECL /** * Open a git repository. * - * The 'path' argument must point to an existing git repository - * folder, e.g. + * The 'path' argument must point to either a git repository + * folder, or an existing work dir. * - * /path/to/my_repo/.git/ (normal repository) - * objects/ - * index - * HEAD - * - * /path/to/bare_repo/ (bare repository) - * objects/ - * index - * HEAD - * - * The method will automatically detect if 'path' is a normal - * or bare repository or fail is 'path' is neither. + * The method will automatically detect if 'path' is a normal + * or bare repository or fail is 'path' is neither. * * @param repository pointer to the repo which will be opened * @param path the path to the repository - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_repository_open(git_repository **repository, const char *path); +/** + * Look for a git repository and copy its path in the given buffer. + * The lookup start from base_path and walk across parent directories + * if nothing has been found. The lookup ends when the first repository + * is found, or when reaching a directory referenced in ceiling_dirs + * or when the filesystem changes (in case across_fs is true). + * + * The method will automatically detect if the repository is bare + * (if there is a repository). + * + * @param repository_path The user allocated buffer which will + * contain the found path. + * + * @param size repository_path size + * + * @param start_path The base path where the lookup starts. + * + * @param across_fs If true, then the lookup will not stop when a + * filesystem device change is detected while exploring parent directories. + * + * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR separated list of + * absolute symbolic link free paths. The lookup will stop when any + * of this paths is reached. Note that the lookup always performs on + * start_path no matter start_path appears in ceiling_dirs ceiling_dirs + * might be NULL (which is equivalent to an empty string) + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_discover( + char *repository_path, + size_t size, + const char *start_path, + int across_fs, + const char *ceiling_dirs); + +enum { + GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), + GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), +}; /** - * Open a git repository by manually specifying all its paths - * - * @param repository pointer to the repo which will be opened - * - * @param git_dir The full path to the repository folder - * e.g. a '.git' folder for live repos, any folder for bare - * Equivalent to $GIT_DIR. - * Cannot be NULL. - * - * @param git_object_directory The full path to the ODB folder. - * the folder where all the loose and packed objects are stored - * Equivalent to $GIT_OBJECT_DIRECTORY. - * If NULL, "$GIT_DIR/objects/" is assumed. - * - * @param git_index_file The full path to the index (dircache) file - * Equivalent to $GIT_INDEX_FILE. - * If NULL, "$GIT_DIR/index" is assumed. - * - * @param git_work_tree The full path to the working tree of the repository, - * if the repository is not bare. - * Equivalent to $GIT_WORK_TREE. - * If NULL, the repository is assumed to be bare. - * - * @return 0 on success; error code otherwise + * Find and open a repository with extended controls. */ -GIT_EXTERN(int) git_repository_open2(git_repository **repository, - const char *git_dir, - const char *git_object_directory, - const char *git_index_file, - const char *git_work_tree); - - -/** - * Open a git repository by manually specifying its paths and - * the object database it will use. - * - * @param repository pointer to the repo which will be opened - * - * @param git_dir The full path to the repository folder - * e.g. a '.git' folder for live repos, any folder for bare - * Equivalent to $GIT_DIR. - * Cannot be NULL. - * - * @param object_database A pointer to a git_odb created & initialized - * by the user (e.g. with custom backends). This object database - * will be owned by the repository and will be automatically free'd. - * It should not be manually free'd by the user, or this - * git_repository object will become invalid. - * - * @param git_index_file The full path to the index (dircache) file - * Equivalent to $GIT_INDEX_FILE. - * If NULL, "$GIT_DIR/index" is assumed. - * - * @param git_work_tree The full path to the working tree of the repository, - * if the repository is not bare. - * Equivalent to $GIT_WORK_TREE. - * If NULL, the repository is assumed to be bare. - * - * @return 0 on success; error code otherwise - */ - -GIT_EXTERN(int) git_repository_open3(git_repository **repository, - const char *git_dir, - git_odb *object_database, - const char *git_index_file, - const char *git_work_tree); - -/** - * Get the object database behind a Git repository - * - * @param repo a repository object - * @return a pointer to the object db - */ -GIT_EXTERN(git_odb *) git_repository_database(git_repository *repo); - -/** - * Open the Index file of a Git repository - * - * This returns a new and unique `git_index` object representing the - * active index for the repository. - * - * This method may be called more than once (e.g. on different threads). - * - * Each returned `git_index` object is independent and suffers no race - * conditions: synchronization is done at the FS level. - * - * Each returned `git_index` object must be manually freed by the user, - * using `git_index_free`. - * - * @param index Pointer where to store the index - * @param repo a repository object - * @return 0 on success; error code if the index could not be opened - */ -GIT_EXTERN(int) git_repository_index(git_index **index, git_repository *repo); +GIT_EXTERN(int) git_repository_open_ext( + git_repository **repo, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs); /** * Free a previously allocated repository * * Note that after a repository is free'd, all the objects it has spawned * will still exist until they are manually closed by the user - * with `git_object_close`, but accessing any of the attributes of + * with `git_object_free`, but accessing any of the attributes of * an object without a backing repository will result in undefined * behavior * @@ -178,17 +102,50 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo); * * TODO: * - Reinit the repository - * - Create config files * * @param repo_out pointer to the repo which will be created or reinitialized * @param path the path to the repository - * @param is_bare if true, a Git repository without a working directory is created - * at the pointed path. If false, provided path will be considered as the working + * @param is_bare if true, a Git repository without a working directory is created + * at the pointed path. If false, provided path will be considered as the working * directory into which the .git directory will be created. * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare); + +/** + * Retrieve and resolve the reference pointed at by HEAD. + * + * @param head_out pointer to the reference which will be retrieved + * @param repo a repository object + * * @return 0 on success; error code otherwise */ -GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare); +GIT_EXTERN(int) git_repository_head(git_reference **head_out, git_repository *repo); + +/** + * Check if a repository's HEAD is detached + * + * A repository's HEAD is detached when it points directly to a commit + * instead of a branch. + * + * @param repo Repo to test + * @return 1 if HEAD is detached, 0 if it's not; error code if there + * was an error. + */ +GIT_EXTERN(int) git_repository_head_detached(git_repository *repo); + +/** + * Check if the current branch is an orphan + * + * An orphan 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 + * code if there was an error + */ +GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo); /** * Check if a repository is empty @@ -203,31 +160,145 @@ GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, GIT_EXTERN(int) git_repository_is_empty(git_repository *repo); /** - * Get the normalized path to the git repository. + * Get the path of this repository * - * @param repo a repository object - * @return absolute path to the git directory + * This is the path of the `.git` folder for normal repositories, + * or of the repository itself for bare repositories. + * + * @param repo A repository object + * @return the path to the repository */ GIT_EXTERN(const char *) git_repository_path(git_repository *repo); /** - * Get the normalized path to the working directory of the repository. + * Get the path of the working directory for this repository * - * If the repository is bare, there is no working directory and NULL we be returned. + * If the repository is bare, this function will always return + * NULL. * - * @param repo a repository object - * @return NULL if the repository is bare; absolute path to the working directory otherwise. + * @param repo A repository object + * @return the path to the working dir, if it exists */ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); +/** + * Set the path to the working directory for this repository + * + * The working directory doesn't need to be the same one + * that contains the `.git` folder for this repository. + * + * If this repository is bare, setting its working directory + * will turn it into a normal repository, capable of performing + * all the common workdir operations (checkout, status, index + * manipulation, etc). + * + * @param repo A repository object + * @param workdir The path to a working directory + * @return 0, or an error code + */ +GIT_EXTERN(int) git_repository_set_workdir(git_repository *repo, const char *workdir); + /** * Check if a repository is bare * * @param repo Repo to test - * @return 1 if the repository is empty, 0 otherwise. + * @return 1 if the repository is bare, 0 otherwise. */ GIT_EXTERN(int) git_repository_is_bare(git_repository *repo); +/** + * Get the configuration file for this repository. + * + * If a configuration file has not been set, the default + * config set for the repository will be returned, including + * global and system configurations (if they are available). + * + * The configuration file must be freed once it's no longer + * being used by the user. + * + * @param out Pointer to store the loaded config file + * @param repo A repository object + * @return 0, or an error code + */ +GIT_EXTERN(int) git_repository_config(git_config **out, git_repository *repo); + +/** + * Set the configuration file for this repository + * + * This configuration file will be used for all configuration + * queries involving this repository. + * + * The repository will keep a reference to the config file; + * the user must still free the config after setting it + * to the repository, or it will leak. + * + * @param repo A repository object + * @param config A Config object + */ +GIT_EXTERN(void) git_repository_set_config(git_repository *repo, git_config *config); + +/** + * Get the Object Database for this repository. + * + * If a custom ODB has not been set, the default + * database for the repository will be returned (the one + * located in `.git/objects`). + * + * The ODB must be freed once it's no longer being used by + * the user. + * + * @param out Pointer to store the loaded ODB + * @param repo A repository object + * @return 0, or an error code + */ +GIT_EXTERN(int) git_repository_odb(git_odb **out, git_repository *repo); + +/** + * Set the Object Database for this repository + * + * The ODB will be used for all object-related operations + * involving this repository. + * + * The repository will keep a reference to the ODB; the user + * must still free the ODB object after setting it to the + * repository, or it will leak. + * + * @param repo A repository object + * @param odb An ODB object + */ +GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb); + +/** + * Get the Index file for this repository. + * + * If a custom index has not been set, the default + * index for the repository will be returned (the one + * located in `.git/index`). + * + * The index must be freed once it's no longer being used by + * the user. + * + * @param out Pointer to store the loaded index + * @param repo A repository object + * @return 0, or an error code + */ +GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); + +/** + * Set the index file for this repository + * + * This index will be used for all index-related operations + * involving this repository. + * + * The repository will keep a reference to the index file; + * the user must still free the index after setting it + * to the repository, or it will leak. + * + * @param repo A repository object + * @param index An index object + */ +GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index f3e0152d4..2e9dc421a 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_revwalk_h__ #define INCLUDE_git_revwalk_h__ @@ -44,28 +26,28 @@ GIT_BEGIN_DECL * and subject to change at any time. * This is the default sorting for new walkers. */ -#define GIT_SORT_NONE (0) +#define GIT_SORT_NONE (0) /** * Sort the repository contents in topological order * (parents before children); this sorting mode * can be combined with time sorting. */ -#define GIT_SORT_TOPOLOGICAL (1 << 0) +#define GIT_SORT_TOPOLOGICAL (1 << 0) /** * Sort the repository contents by commit time; * this sorting mode can be combined with * topological sorting. */ -#define GIT_SORT_TIME (1 << 1) +#define GIT_SORT_TIME (1 << 1) /** * Iterate through the repository contents in reverse * order; this sorting mode can be combined with * any of the above. */ -#define GIT_SORT_REVERSE (1 << 2) +#define GIT_SORT_REVERSE (1 << 2) /** * Allocate a new revision walker to iterate through a repo. @@ -83,7 +65,7 @@ GIT_BEGIN_DECL * * @param walker pointer to the new revision walker * @param repo the repo to walk through - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_revwalk_new(git_revwalk **walker, git_repository *repo); @@ -113,12 +95,34 @@ GIT_EXTERN(void) git_revwalk_reset(git_revwalk *walker); * must be pushed the repository before a walk can * be started. * - * @param walker the walker being used for the traversal. + * @param walk the walker being used for the traversal. * @param oid the oid of the commit to start from. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *oid); +/** + * Push matching references + * + * The OIDs pointed to by the references that match the given glob + * pattern will be pushed to the revision walker. + * + * A leading 'refs/' is implied it not present as well as a trailing + * '/ *' if the glob lacks '?', '*' or '['. + * + * @param walk the walker being used for the traversal + * @param glob the glob pattern references should match + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_push_glob(git_revwalk *walk, const char *glob); + +/** + * Push the repository's HEAD + * + * @param walk the walker being used for the traversal + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_push_head(git_revwalk *walk); /** * Mark a commit (and its ancestors) uninteresting for the output. @@ -129,12 +133,58 @@ GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *oid); * The resolved commit and all its parents will be hidden from the * output on the revision walk. * - * @param walker the walker being used for the traversal. - * @param commit the commit that will be ignored during the traversal - * @return 0 on success; error code otherwise + * @param walk the walker being used for the traversal. + * @param oid the oid of commit that will be ignored during the traversal + * @return 0 or an error code */ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid); +/** + * Hide matching references. + * + * The OIDs pointed to by the references that match the given glob + * pattern and their ancestors will be hidden from the output on the + * revision walk. + * + * A leading 'refs/' is implied it not present as well as a trailing + * '/ *' if the glob lacks '?', '*' or '['. + * + * @param walk the walker being used for the traversal + * @param glob the glob pattern references should match + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_hide_glob(git_revwalk *walk, const char *glob); + +/** + * Hide the repository's HEAD + * + * @param walk the walker being used for the traversal + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_hide_head(git_revwalk *walk); + +/** + * Push the OID pointed to by a reference + * + * The reference must point to a commit. + * + * @param walk the walker being used for the traversal + * @param refname the reference to push + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_push_ref(git_revwalk *walk, const char *refname); + +/** + * Hide the OID pointed to by a reference + * + * The reference must point to a commit. + * + * @param walk the walker being used for the traversal + * @param refname the reference to hide + * @return 0 or an error code + */ +GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname); + /** * Get the next commit from the revision walk. * @@ -150,8 +200,8 @@ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid); * * @param oid Pointer where to store the oid of the next commit * @param walk the walker to pop the commit from. - * @return GIT_SUCCESS if the next commit was found; - * GIT_EREVWALKOVER if there are no commits left to iterate + * @return 0 if the next commit was found; + * GIT_REVWALKOVER if there are no commits left to iterate */ GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk); @@ -169,7 +219,7 @@ GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode); /** * Free a revision walker previously allocated. * - * @param walk traversal handle to close. If NULL nothing occurs. + * @param walk traversal handle to close. If NULL nothing occurs. */ GIT_EXTERN(void) git_revwalk_free(git_revwalk *walk); diff --git a/include/git2/signature.h b/include/git2/signature.h index 44d1f285e..cbf94269f 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_signature_h__ #define INCLUDE_git_signature_h__ @@ -41,23 +23,25 @@ GIT_BEGIN_DECL * Create a new action signature. The signature must be freed * manually or using git_signature_free * + * @param sig_out new signature, in case of error NULL * @param name name of the person - * @param mail email of the person + * @param email email of the person * @param time time when the action happened * @param offset timezone offset in minutes for the time - * @return the new sig, NULL on out of memory + * @return 0 or an error code */ -GIT_EXTERN(git_signature *) git_signature_new(const char *name, const char *email, git_time_t time, int offset); +GIT_EXTERN(int) git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset); /** * Create a new action signature with a timestamp of 'now'. The * signature must be freed manually or using git_signature_free * + * @param sig_out new signature, in case of error NULL * @param name name of the person * @param email email of the person - * @return the new sig, NULL on out of memory + * @return 0 or an error code */ -GIT_EXTERN(git_signature *) git_signature_now(const char *name, const char *email); +GIT_EXTERN(int) git_signature_now(git_signature **sig_out, const char *name, const char *email); /** diff --git a/include/git2/status.h b/include/git2/status.h new file mode 100644 index 000000000..6a424dfd6 --- /dev/null +++ b/include/git2/status.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_status_h__ +#define INCLUDE_git_status_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/status.h + * @brief Git file status routines + * @defgroup git_status Git file status routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_STATUS_CURRENT = 0, + + GIT_STATUS_INDEX_NEW = (1 << 0), + GIT_STATUS_INDEX_MODIFIED = (1 << 1), + GIT_STATUS_INDEX_DELETED = (1 << 2), + + GIT_STATUS_WT_NEW = (1 << 3), + GIT_STATUS_WT_MODIFIED = (1 << 4), + GIT_STATUS_WT_DELETED = (1 << 5), + + GIT_STATUS_IGNORED = (1 << 6), +}; + +/** + * Gather file statuses and run a callback for each one. + * + * The callback is passed the path of the file, the status and the data + * pointer passed to this function. If the callback returns something other + * than 0, this function will return that value. + * + * @param repo a repository object + * @param callback the function to call on each file + * @return 0 on success or the return value of the callback that was non-zero + */ +GIT_EXTERN(int) git_status_foreach( + git_repository *repo, + int (*callback)(const char *, unsigned int, void *), + void *payload); + +/** + * Select the files on which to report status. + * + * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the + * rough equivalent of `git status --porcelain` where each file + * will receive a callback indicating its status in the index and + * in the workdir. + * - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index + * side of status. The status of the index contents relative to + * the HEAD will be given. + * - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the + * workdir side of status, reporting the status of workdir content + * relative to the index. + * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only + * followed by workdir-only, causing two callbacks to be issued + * per file (first index then workdir). This is slightly more + * efficient than making separate calls. This makes it easier to + * emulate the output of a plain `git status`. + */ +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; + +/** + * Flags to control status callbacks + * + * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should + * be made on untracked files. These will only be made if the + * workdir files are included in the status "show" option. + * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should + * get callbacks. Again, these callbacks will only be made if + * the workdir files are included in the status "show" option. + * Right now, there is no option to include all files in + * directories that are ignored completely. + * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback + * should be made even on unmodified files. + * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories + * which appear to be submodules should just be skipped over. + * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the + * contents of untracked directories should be included in the + * status. Normally if an entire directory is new, then just + * the top-level directory will be included (with a trailing + * slash on the entry name). Given this flag, the directory + * itself will not be included, but all the files in it will. + */ + +enum { + GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0), + GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1), + GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), + GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3), + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), +}; + +/** + * Options to control how callbacks will be made by + * `git_status_foreach_ext()`. + */ +typedef struct { + git_status_show_t show; + unsigned int flags; + git_strarray pathspec; +} git_status_options; + +/** + * Gather file status information and run callbacks as requested. + */ +GIT_EXTERN(int) git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + int (*callback)(const char *, unsigned int, void *), + void *payload); + +/** + * Get file status for a single file + * + * @param status_flags the status value + * @param repo a repository object + * @param path the file to retrieve status for, rooted at the repo's workdir + * @return GIT_EINVALIDPATH when `path` points at a folder, GIT_ENOTFOUND when + * the file doesn't exist in any of HEAD, the index or the worktree, + * 0 otherwise + */ +GIT_EXTERN(int) git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path); + +/** + * Test if the ignore rules apply to a given file. + * + * This function simply checks the ignore rules to see if they would apply + * to the given file. Unlike git_status_file(), this indicates if the file + * would be ignored regardless of whether the file is already in the index + * or in the repository. + * + * @param ignored boolean returning 0 if the file is not ignored, 1 if it is + * @param repo a repository object + * @param path the file to check ignores for, rooted at the repo's workdir. + * @return 0 if ignore rules could be processed for the file (regardless + * of whether it exists or not), or an error < 0 if they could not. + */ +GIT_EXTERN(int) git_status_should_ignore( + int *ignored, + git_repository *repo, + const char *path); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/stdint.h b/include/git2/stdint.h new file mode 100644 index 000000000..c66fbb817 --- /dev/null +++ b/include/git2/stdint.h @@ -0,0 +1,247 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are 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 of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/include/git2/submodule.h b/include/git2/submodule.h new file mode 100644 index 000000000..f65911a3b --- /dev/null +++ b/include/git2/submodule.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_submodule_h__ +#define INCLUDE_git_submodule_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" + +/** + * @file git2/submodule.h + * @brief Git submodule management utilities + * @defgroup git_submodule Git submodule management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +typedef enum { + GIT_SUBMODULE_UPDATE_CHECKOUT = 0, + GIT_SUBMODULE_UPDATE_REBASE = 1, + GIT_SUBMODULE_UPDATE_MERGE = 2 +} git_submodule_update_t; + +typedef enum { + GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */ + GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */ +} git_submodule_ignore_t; + +/** + * Description of submodule + * + * This record describes a submodule found in a repository. There + * should be an entry for every submodule found in the HEAD and for + * every submodule described in .gitmodules. The fields are as follows: + * + * - `name` is the name of the submodule from .gitmodules. + * - `path` is the path to the submodule from the repo working directory. + * It is almost always the same as `name`. + * - `url` is the url for the submodule. + * - `oid` is the HEAD SHA1 for the submodule. + * - `update` is a value from above - see gitmodules(5) update. + * - `ignore` is a value from above - see gitmodules(5) ignore. + * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. + * - `refcount` is for internal use. + * + * If the submodule has been added to .gitmodules but not yet git added, + * then the `oid` will be zero. If the submodule has been deleted, but + * the delete has not been committed yet, then the `oid` will be set, but + * the `url` will be NULL. + */ +typedef struct { + char *name; + char *path; + char *url; + git_oid oid; /* sha1 of submodule HEAD ref or zero if not committed */ + git_submodule_update_t update; + git_submodule_ignore_t ignore; + int fetch_recurse; + int refcount; +} git_submodule; + +/** + * Iterate over all submodules of a repository. + * + * @param repo The repository + * @param callback Function to be called with the name of each submodule. + * Return a non-zero value to terminate the iteration. + * @param payload Extra data to pass to callback + * @return 0 on success, -1 on error, or non-zero return value of callback + */ +GIT_EXTERN(int) git_submodule_foreach( + git_repository *repo, + int (*callback)(const char *name, void *payload), + void *payload); + +/** + * Lookup submodule information by name or path. + * + * Given either the submodule name or path (they are usually the same), + * this returns a structure describing the submodule. If the submodule + * does not exist, this will return GIT_ENOTFOUND and set the submodule + * pointer to NULL. + * + * @param submodule Pointer to submodule description object pointer.. + * @param repo The repository. + * @param name The name of the submodule. Trailing slashes will be ignored. + * @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error + */ +GIT_EXTERN(int) git_submodule_lookup( + git_submodule **submodule, + git_repository *repo, + const char *name); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/tag.h b/include/git2/tag.h index 3fc6b4499..13dc145b6 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_tag_h__ #define INCLUDE_git_tag_h__ @@ -45,17 +27,34 @@ GIT_BEGIN_DECL * @param tag pointer to the looked up tag * @param repo the repo to use when locating the tag. * @param id identity of the tag to locate. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oid *id) { return git_object_lookup((git_object **)tag, repo, id, (git_otype)GIT_OBJ_TAG); } +/** + * Lookup a tag object from the repository, + * given a prefix of its identifier (short id). + * + * @see git_object_lookup_prefix + * + * @param tag pointer to the looked up tag + * @param repo the repo to use when locating the tag. + * @param id identity of the tag to locate. + * @param len the length of the short identifier + * @return 0 or an error code + */ +GIT_INLINE(int) git_tag_lookup_prefix(git_tag **tag, git_repository *repo, const git_oid *id, unsigned int len) +{ + return git_object_lookup_prefix((git_object **)tag, repo, id, len, (git_otype)GIT_OBJ_TAG); +} + /** * Close an open tag * - * This is a wrapper around git_object_close() + * This is a wrapper around git_object_free() * * IMPORTANT: * It *is* necessary to call this method when you stop @@ -64,9 +63,9 @@ GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oi * @param tag the tag to close */ -GIT_INLINE(void) git_tag_close(git_tag *tag) +GIT_INLINE(void) git_tag_free(git_tag *tag) { - git_object_close((git_object *) tag); + git_object_free((git_object *) tag); } @@ -86,9 +85,9 @@ GIT_EXTERN(const git_oid *) git_tag_id(git_tag *tag); * * @param target pointer where to store the target * @param tag a previously loaded tag. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *t); +GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *tag); /** * Get the OID of the tagged object of a tag @@ -96,7 +95,7 @@ GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *t); * @param tag a previously loaded tag. * @return pointer to the OID */ -GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *t); +GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *tag); /** * Get the type of a tag's tagged object @@ -104,7 +103,7 @@ GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *t); * @param tag a previously loaded tag. * @return type of the tagged object */ -GIT_EXTERN(git_otype) git_tag_type(git_tag *t); +GIT_EXTERN(git_otype) git_tag_type(git_tag *tag); /** * Get the name of a tag @@ -112,7 +111,7 @@ GIT_EXTERN(git_otype) git_tag_type(git_tag *t); * @param tag a previously loaded tag. * @return name of the tag */ -GIT_EXTERN(const char *) git_tag_name(git_tag *t); +GIT_EXTERN(const char *) git_tag_name(git_tag *tag); /** * Get the tagger (author) of a tag @@ -120,7 +119,7 @@ GIT_EXTERN(const char *) git_tag_name(git_tag *t); * @param tag a previously loaded tag. * @return reference to the tag's author */ -GIT_EXTERN(const git_signature *) git_tag_tagger(git_tag *t); +GIT_EXTERN(const git_signature *) git_tag_tagger(git_tag *tag); /** * Get the message of a tag @@ -128,138 +127,101 @@ GIT_EXTERN(const git_signature *) git_tag_tagger(git_tag *t); * @param tag a previously loaded tag. * @return message of the tag */ -GIT_EXTERN(const char *) git_tag_message(git_tag *t); +GIT_EXTERN(const char *) git_tag_message(git_tag *tag); /** - * Create a new tag in the repository from an OID + * Create a new tag in the repository from an object + * + * A new reference will also be created pointing to + * this tag object. If `force` is true and a reference + * already exists with the given name, it'll be replaced. + * + * The message will be cleaned up from excess whitespace + * it will be made sure that the last line ends with a '\n'. * * @param oid Pointer where to store the OID of the - * newly created tag + * newly created tag. If the tag already exists, this parameter + * will be the oid of the existing tag, and the function will + * return a GIT_EEXISTS error code. * * @param repo Repository where to store the tag * * @param tag_name Name for the tag; this name is validated - * for consistency. It should also not conflict with an + * for consistency. It should also not conflict with an * already existing tag name * - * @param target OID to which this tag points; note that no - * validation is done on this OID. Use the _o version of this - * method to assure a proper object is being tagged - * - * @param target_type Type of the tagged OID; note that no - * validation is performed here either + * @param target Object to which this tag points. This object + * must belong to the given `repo`. * * @param tagger Signature of the tagger for this tag, and - * of the tagging time + * of the tagging time * * @param message Full message for this tag * - * @return 0 on success; error code otherwise. + * @param force Overwrite existing references + * + * @return 0 or an error code * A tag object is written to the ODB, and a proper reference * is written in the /refs/tags folder, pointing to it */ GIT_EXTERN(int) git_tag_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_oid *target, - git_otype target_type, - const git_signature *tagger, - const char *message); - - -/** - * Create a new tag in the repository from an existing - * `git_object` instance - * - * This method replaces the `target` and `target_type` - * paremeters of `git_tag_create` by a single instance - * of a `const git_object *`, which is assured to be - * a proper object in the ODB and hence will create - * a valid tag - * - * @see git_tag_create - */ -GIT_EXTERN(int) git_tag_create_o( git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, const git_signature *tagger, - const char *message); + const char *message, + int force); /** * Create a new tag in the repository from a buffer * * @param oid Pointer where to store the OID of the newly created tag - * * @param repo Repository where to store the tag - * * @param buffer Raw tag data + * @param force Overwrite existing tags + * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_tag_create_frombuffer( git_oid *oid, git_repository *repo, - const char *buffer); + const char *buffer, + int force); /** - * Create a new tag in the repository from an OID - * and overwrite an already existing tag reference, if any. + * Create a new lightweight tag pointing at a target object * - * @param oid Pointer where to store the OID of the - * newly created tag + * A new direct reference will be created pointing to + * this target object. If `force` is true and a reference + * already exists with the given name, it'll be replaced. * - * @param repo Repository where to store the tag + * @param oid Pointer where to store the OID of the provided + * target object. If the tag already exists, this parameter + * will be filled with the oid of the existing pointed object + * and the function will return a GIT_EEXISTS error code. + * + * @param repo Repository where to store the lightweight tag * * @param tag_name Name for the tag; this name is validated - * for consistency. + * for consistency. It should also not conflict with an + * already existing tag name * - * @param target OID to which this tag points; note that no - * validation is done on this OID. Use the _fo version of this - * method to assure a proper object is being tagged + * @param target Object to which this tag points. This object + * must belong to the given `repo`. * - * @param target_type Type of the tagged OID; note that no - * validation is performed here either + * @param force Overwrite existing references * - * @param tagger Signature of the tagger for this tag, and - * of the tagging time - * - * @param message Full message for this tag - * - * @return 0 on success; error code otherwise. - * A tag object is written to the ODB, and a proper reference - * is written in the /refs/tags folder, pointing to it + * @return 0 or an error code + * A proper reference is written in the /refs/tags folder, + * pointing to the provided target object */ -GIT_EXTERN(int) git_tag_create_f( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_oid *target, - git_otype target_type, - const git_signature *tagger, - const char *message); - -/** - * Create a new tag in the repository from an existing - * `git_object` instance and overwrite an already existing - * tag reference, if any. - * - * This method replaces the `target` and `target_type` - * paremeters of `git_tag_create_f` by a single instance - * of a `const git_object *`, which is assured to be - * a proper object in the ODB and hence will create - * a valid tag - * - * @see git_tag_create_f - */ -GIT_EXTERN(int) git_tag_create_fo( +GIT_EXTERN(int) git_tag_create_lightweight( git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, - const git_signature *tagger, - const char *message); + int force); /** * Delete an existing tag reference. @@ -269,7 +231,7 @@ GIT_EXTERN(int) git_tag_create_fo( * @param tag_name Name of the tag to be deleted; * this name is validated for consistency. * - * @return 0 on success; error code otherwise. + * @return 0 or an error code */ GIT_EXTERN(int) git_tag_delete( git_repository *repo, @@ -283,15 +245,53 @@ GIT_EXTERN(int) git_tag_delete( * should be free'd manually when no longer needed, using * `git_strarray_free`. * - * @param array Pointer to a git_strarray structure where + * @param tag_names Pointer to a git_strarray structure where * the tag names will be stored * @param repo Repository where to find the tags - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_tag_list( git_strarray *tag_names, git_repository *repo); +/** + * Fill a list with all the tags in the Repository + * which name match a defined pattern + * + * If an empty pattern is provided, all the tags + * will be returned. + * + * The string array will be filled with the names of the + * matching tags; these values are owned by the user and + * should be free'd manually when no longer needed, using + * `git_strarray_free`. + * + * @param tag_names Pointer to a git_strarray structure where + * the tag names will be stored + * @param pattern Standard fnmatch pattern + * @param repo Repository where to find the tags + * @return 0 or an error code + */ +GIT_EXTERN(int) git_tag_list_match( + git_strarray *tag_names, + const char *pattern, + git_repository *repo); + +/** + * Recursively peel a tag until a non tag git_object + * is met + * + * The retrieved `tag_target` object is owned by the repository + * and should be closed with the `git_object_free` method. + * + * @param tag_target Pointer to the peeled git_object + * @param tag The tag to be processed + * @return 0 or an error code + */ +GIT_EXTERN(int) git_tag_peel( + git_object **tag_target, + git_tag *tag); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/thread-utils.h b/include/git2/thread-utils.h deleted file mode 100644 index e26876bea..000000000 --- a/include/git2/thread-utils.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#ifndef INCLUDE_git_thread_utils_h__ -#define INCLUDE_git_thread_utils_h__ - -/* - * How TLS works is compiler+platform dependant - * Sources: http://en.wikipedia.org/wiki/Thread-Specific_Storage - * http://predef.sourceforge.net/precomp.html - */ - -#define GIT_HAS_TLS 1 - -#if defined(__APPLE__) && defined(__MACH__) -# undef GIT_TLS -# define GIT_TLS - -#elif defined(__GNUC__) || \ - defined(__SUNPRO_C) || \ - defined(__SUNPRO_CC) || \ - defined(__xlc__) || \ - defined(__xlC__) -# define GIT_TLS __thread - -#elif defined(__INTEL_COMPILER) -# if defined(_WIN32) || defined(_WIN32_CE) -# define GIT_TLS __declspec(thread) -# else -# define GIT_TLS __thread -# endif - -#elif defined(_WIN32) || \ - defined(_WIN32_CE) || \ - defined(__BORLANDC__) -# define GIT_TLS __declspec(thread) - -#else -# undef GIT_HAS_TLS -# define GIT_TLS /* nothing: tls vars are thread-global */ -#endif - -/* sparse and cygwin don't grok thread-local variables */ -#if defined(__CHECKER__) || defined(__CYGWIN__) -# undef GIT_HAS_TLS -# undef GIT_TLS -# define GIT_TLS -#endif - -#endif /* INCLUDE_git_thread_utils_h__ */ diff --git a/include/git2/threads.h b/include/git2/threads.h new file mode 100644 index 000000000..567a10487 --- /dev/null +++ b/include/git2/threads.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_threads_h__ +#define INCLUDE_git_threads_h__ + +#include "common.h" + +/** + * @file git2/threads.h + * @brief Library level thread functions + * @defgroup git_thread Threading functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Init the threading system. + * + * If libgit2 has been built with GIT_THREADS + * on, this function must be called once before + * any other library functions. + * + * If libgit2 has been built without GIT_THREADS + * support, this function is a no-op. + */ +GIT_EXTERN(void) git_threads_init(void); + +/** + * Shutdown the threading system. + * + * If libgit2 has been built with GIT_THREADS + * on, this function must be called before shutting + * down the library. + * + * If libgit2 has been built without GIT_THREADS + * support, this function is a no-op. + */ +GIT_EXTERN(void) git_threads_shutdown(void); + +/** @} */ +GIT_END_DECL +#endif + diff --git a/include/git2/tree.h b/include/git2/tree.h index 0caf60a48..8f62e752a 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_tree_h__ #define INCLUDE_git_tree_h__ @@ -45,17 +27,34 @@ GIT_BEGIN_DECL * @param tree pointer to the looked up tree * @param repo the repo to use when locating the tree. * @param id identity of the tree to locate. - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_INLINE(int) git_tree_lookup(git_tree **tree, git_repository *repo, const git_oid *id) { return git_object_lookup((git_object **)tree, repo, id, GIT_OBJ_TREE); } +/** + * Lookup a tree object from the repository, + * given a prefix of its identifier (short id). + * + * @see git_object_lookup_prefix + * + * @param tree pointer to the looked up tree + * @param repo the repo to use when locating the tree. + * @param id identity of the tree to locate. + * @param len the length of the short identifier + * @return 0 or an error code + */ +GIT_INLINE(int) git_tree_lookup_prefix(git_tree **tree, git_repository *repo, const git_oid *id, unsigned int len) +{ + return git_object_lookup_prefix((git_object **)tree, repo, id, len, GIT_OBJ_TREE); +} + /** * Close an open tree * - * This is a wrapper around git_object_close() + * This is a wrapper around git_object_free() * * IMPORTANT: * It *is* necessary to call this method when you stop @@ -64,9 +63,9 @@ GIT_INLINE(int) git_tree_lookup(git_tree **tree, git_repository *repo, const git * @param tree the tree to close */ -GIT_INLINE(void) git_tree_close(git_tree *tree) +GIT_INLINE(void) git_tree_free(git_tree *tree) { - git_object_close((git_object *) tree); + git_object_free((git_object *) tree); } @@ -84,7 +83,7 @@ GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree); * @param tree a previously loaded tree. * @return the number of entries in the tree */ -GIT_EXTERN(size_t) git_tree_entrycount(git_tree *tree); +GIT_EXTERN(unsigned int) git_tree_entrycount(git_tree *tree); /** * Lookup a tree entry by its filename @@ -102,7 +101,7 @@ GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const c * @param idx the position in the entry list * @return the tree entry; NULL if not found */ -GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, int idx); +GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, unsigned int idx); /** * Get the UNIX file attributes of a tree entry @@ -128,15 +127,23 @@ GIT_EXTERN(const char *) git_tree_entry_name(const git_tree_entry *entry); */ GIT_EXTERN(const git_oid *) git_tree_entry_id(const git_tree_entry *entry); +/** + * Get the type of the object pointed by the entry + * + * @param entry a tree entry + * @return the type of the pointed object + */ +GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry); + /** * Convert a tree entry to the git_object it points too. * * @param object pointer to the converted object * @param repo repository where to lookup the pointed object * @param entry a tree entry - * @return 0 on success; error code otherwise + * @return 0 or an error code */ -GIT_EXTERN(int) git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry); +GIT_EXTERN(int) git_tree_entry_to_object(git_object **object_out, git_repository *repo, const git_tree_entry *entry); /** * Write a tree to the ODB from the index file @@ -152,7 +159,7 @@ GIT_EXTERN(int) git_tree_entry_2object(git_object **object_out, git_repository * * * @param oid Pointer where to store the written tree * @param index Index to write - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index); @@ -165,13 +172,13 @@ GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index); * * If the `source` parameter is not NULL, the tree builder * will be initialized with the entries of the given tree. - * + * * If the `source` parameter is NULL, the tree builder will * have no entries and will have to be filled manually. * * @param builder_p Pointer where to store the tree builder * @param source Source tree to initialize the builder (optional) - * @return 0 on sucess; error code otherwise + * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source); @@ -222,7 +229,7 @@ GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(git_treebuilder *bld, con * @param filename Filename of the entry * @param id SHA1 oid of the entry * @param attributes Folder attributes of the entry - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes); @@ -257,10 +264,55 @@ GIT_EXTERN(void) git_treebuilder_filter(git_treebuilder *bld, int (*filter)(cons * @param oid Pointer where to store the written OID * @param repo Repository where to store the object * @param bld Tree builder to write - * @return 0 on success; error code otherwise + * @return 0 or an error code */ GIT_EXTERN(int) git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld); +/** + * Retrieve a subtree contained in a tree, given its + * relative path. + * + * The returned tree is owned by the repository and + * should be closed with the `git_object_free` method. + * + * @param subtree Pointer where to store the subtree + * @param root A previously loaded tree which will be the root of the relative path + * @param subtree_path Path to the contained subtree + * @return 0 on success; GIT_ENOTFOUND if the path does not lead to a subtree + */ +GIT_EXTERN(int) git_tree_get_subtree(git_tree **subtree, git_tree *root, const char *subtree_path); + +/** Callback for the tree traversal method */ +typedef int (*git_treewalk_cb)(const char *root, git_tree_entry *entry, void *payload); + +/** Tree traversal modes */ +enum git_treewalk_mode { + GIT_TREEWALK_PRE = 0, /* Pre-order */ + GIT_TREEWALK_POST = 1, /* Post-order */ +}; + +/** + * Traverse the entries in a tree and its subtrees in + * post or pre order + * + * The entries will be traversed in the specified order, + * children subtrees will be automatically loaded as required, + * and the `callback` will be called once per entry with + * the current (relative) root for the entry and the entry + * data itself. + * + * If the callback returns a negative value, the passed entry + * will be skipped on the traversal. + * + * @param tree The tree to walk + * @param callback Function to call on each tree entry + * @param mode Traversal mode (pre or post-order) + * @param payload Opaque pointer to be passed on each callback + * @return 0 or an error code + */ +GIT_EXTERN(int) git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload); + /** @} */ + GIT_END_DECL #endif diff --git a/include/git2/types.h b/include/git2/types.h index 8fdfd7fc8..93823abf7 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -1,30 +1,14 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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_types_h__ #define INCLUDE_git_types_h__ +#include "common.h" + /** * @file git2/types.h * @brief libgit2 base & compatibility types @@ -55,16 +39,21 @@ GIT_BEGIN_DECL #if defined(_MSC_VER) typedef __int64 git_off_t; -typedef __time64_t git_time_t; +typedef __time64_t git_time_t; #elif defined(__MINGW32__) typedef off64_t git_off_t; typedef __time64_t git_time_t; -#else /* POSIX */ +#elif defined(__HAIKU__) -/* +typedef __haiku_std_int64 git_off_t; +typedef __haiku_std_int64 git_time_t; + +#else /* POSIX */ + +/* * Note: Can't use off_t since if a client program includes * before us (directly or indirectly), they'll get 32 bit off_t in their client * app, even though /we/ define _FILE_OFFSET_BITS=64. @@ -77,15 +66,15 @@ typedef int64_t git_time_t; /** Basic type (loose or packed) of any Git object. */ typedef enum { GIT_OBJ_ANY = -2, /**< Object can be any of the following */ - GIT_OBJ_BAD = -1, /**< Object is invalid. */ - GIT_OBJ__EXT1 = 0, /**< Reserved for future use. */ - GIT_OBJ_COMMIT = 1, /**< A commit object. */ - GIT_OBJ_TREE = 2, /**< A tree (directory listing) object. */ - GIT_OBJ_BLOB = 3, /**< A file revision object. */ - GIT_OBJ_TAG = 4, /**< An annotated tag object. */ - GIT_OBJ__EXT2 = 5, /**< Reserved for future use. */ - GIT_OBJ_OFS_DELTA = 6, /**< A delta, base is given by an offset. */ - GIT_OBJ_REF_DELTA = 7, /**< A delta, base is given by object id. */ + GIT_OBJ_BAD = -1, /**< Object is invalid. */ + GIT_OBJ__EXT1 = 0, /**< Reserved for future use. */ + GIT_OBJ_COMMIT = 1, /**< A commit object. */ + GIT_OBJ_TREE = 2, /**< A tree (directory listing) object. */ + GIT_OBJ_BLOB = 3, /**< A file revision object. */ + GIT_OBJ_TAG = 4, /**< An annotated tag object. */ + GIT_OBJ__EXT2 = 5, /**< Reserved for future use. */ + GIT_OBJ_OFS_DELTA = 6, /**< A delta, base is given by an offset. */ + GIT_OBJ_REF_DELTA = 7, /**< A delta, base is given by object id. */ } git_otype; /** An open object database handle. */ @@ -139,6 +128,15 @@ typedef struct git_config git_config; /** Interface to access a configuration file */ typedef struct git_config_file git_config_file; +/** Representation of a reference log entry */ +typedef struct git_reflog_entry git_reflog_entry; + +/** Representation of a reference log */ +typedef struct git_reflog git_reflog; + +/** Representation of a git note */ +typedef struct git_note git_note; + /** Time in a signature */ typedef struct git_time { git_time_t time; /** time in seconds from epoch */ @@ -163,7 +161,18 @@ typedef enum { GIT_REF_PACKED = 4, GIT_REF_HAS_PEEL = 8, GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED, -} git_rtype; +} git_ref_t; + +/** Basic type of any Git branch. */ +typedef enum { + GIT_BRANCH_LOCAL = 1, + GIT_BRANCH_REMOTE = 2, +} git_branch_t; + +typedef struct git_refspec git_refspec; +typedef struct git_remote git_remote; + +typedef struct git_remote_head git_remote_head; /** @} */ GIT_END_DECL diff --git a/include/git2/version.h b/include/git2/version.h new file mode 100644 index 000000000..8edbe323c --- /dev/null +++ b/include/git2/version.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_version_h__ +#define INCLUDE_git_version_h__ + +#define LIBGIT2_VERSION "0.17.0" +#define LIBGIT2_VER_MAJOR 0 +#define LIBGIT2_VER_MINOR 17 +#define LIBGIT2_VER_REVISION 0 + +#endif diff --git a/include/git2/windows.h b/include/git2/windows.h new file mode 100644 index 000000000..8b743f0aa --- /dev/null +++ b/include/git2/windows.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_windows_h__ +#define INCLUDE_git_windows_h__ + +#include "common.h" + +/** + * @file git2/windows.h + * @brief Windows-specific functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Set the active codepage for Windows syscalls + * + * All syscalls performed by the library will assume + * this codepage when converting paths and strings + * to use by the Windows kernel. + * + * The default value of UTF-8 will work automatically + * with most Git repositories created on Unix systems. + * + * This settings needs only be changed when working + * with repositories that contain paths in specific, + * non-UTF codepages. + * + * A full list of all available codepage identifiers may + * be found at: + * + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx + * + * @param codepage numeric codepage identifier + */ +GIT_EXTERN(void) gitwin_set_codepage(unsigned int codepage); + +/** + * Return the active codepage for Windows syscalls + * + * @return numeric codepage identifier + */ +GIT_EXTERN(unsigned int) gitwin_get_codepage(void); + +/** + * Set the active Windows codepage to UTF-8 (this is + * the default value) + */ +GIT_EXTERN(void) gitwin_set_utf8(void); + +/** @} */ +GIT_END_DECL +#endif + diff --git a/include/git2/zlib.h b/include/git2/zlib.h deleted file mode 100644 index 493566340..000000000 --- a/include/git2/zlib.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#ifndef INCLUDE_git_zlib_h__ -#define INCLUDE_git_zlib_h__ - -#include - -/** - * @file git2/zlib.h - * @brief Git data compression routines - * @defgroup git_zlib Git data compression routines - * @ingroup Git - * @{ - */ -GIT_BEGIN_DECL - -#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 -/** - * deflateBound returns an upper bound on the compressed size. - * - * This is a stub function used when zlib does not supply the - * deflateBound() implementation itself. - * - * @param stream the stream pointer. - * @param s total length of the source data (in bytes). - * @return maximum length of the compressed data. - */ -GIT_INLINE(size_t) deflateBound(z_streamp stream, size_t s) -{ - return (s + ((s + 7) >> 3) + ((s + 63) >> 6) + 11); -} -#endif - -/** @} */ -GIT_END_DECL -#endif diff --git a/libgit2.pc.in b/libgit2.pc.in index ece5f2b8e..6165ad678 100644 --- a/libgit2.pc.in +++ b/libgit2.pc.in @@ -1,11 +1,9 @@ -prefix=@prefix@ -exec_prefix=${prefix} -libdir=@libdir@ -includedir=${prefix}/include +libdir=@CMAKE_INSTALL_PREFIX@/@INSTALL_LIB@ +includedir=@CMAKE_INSTALL_PREFIX@/@INSTALL_INC@ Name: libgit2 Description: The git library, take 2 -Version: @version@ +Version: @LIBGIT2_VERSION_STRING@ Requires: libcrypto Libs: -L${libdir} -lgit2 -lz -lcrypto Cflags: -I${includedir} diff --git a/packaging/rpm/README b/packaging/rpm/README new file mode 100644 index 000000000..1a6410b16 --- /dev/null +++ b/packaging/rpm/README @@ -0,0 +1,6 @@ +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 new file mode 100644 index 000000000..a6e82b241 --- /dev/null +++ b/packaging/rpm/libgit2.spec @@ -0,0 +1,106 @@ +# +# 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} \ + -DINSTALL_LIB:PATH=%{_libdir} +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/src/attr.c b/src/attr.c new file mode 100644 index 000000000..fb6651196 --- /dev/null +++ b/src/attr.c @@ -0,0 +1,677 @@ +#include "repository.h" +#include "fileops.h" +#include "config.h" +#include + +GIT__USE_STRMAP; + +static int collect_attr_files( + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files); + + +int git_attr_get( + const char **value, + git_repository *repo, + uint32_t flags, + const char *pathname, + const char *name) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + + *value = NULL; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + int pos = git_vector_bsearch(&rule->assigns, &attr); + if (pos >= 0) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto cleanup; + } + } + } + +cleanup: + git_vector_free(&files); + git_attr_path__free(&path); + + return error; +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many( + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + 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; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + info = git__calloc(num_attr, sizeof(attr_get_many_info)); + GITERR_CHECK_ALLOC(info); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + int pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + pos = git_vector_bsearch(&rule->assigns, &info[k].name); + if (pos >= 0) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + +cleanup: + git_vector_free(&files); + git_attr_path__free(&path); + git__free(info); + + return error; +} + + +int git_attr_foreach( + git_repository *repo, + uint32_t flags, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_strmap *seen = NULL; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + seen = git_strmap_alloc(); + GITERR_CHECK_ALLOC(seen); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_strmap_exists(seen, assign->name)) + continue; + + git_strmap_insert(seen, assign->name, assign, error); + if (error >= 0) + error = callback(assign->name, assign->value, payload); + + if (error != 0) + goto cleanup; + } + } + } + +cleanup: + git_strmap_free(seen); + git_vector_free(&files); + git_attr_path__free(&path); + + return error; +} + + +int git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values) +{ + int error; + git_attr_rule *macro = NULL; + git_pool *pool; + + if (git_attr_cache__init(repo) < 0) + return -1; + + macro = git__calloc(1, sizeof(git_attr_rule)); + GITERR_CHECK_ALLOC(macro); + + pool = &git_repository_attr_cache(repo)->pool; + + macro->match.pattern = git_pool_strdup(pool, name); + GITERR_CHECK_ALLOC(macro->match.pattern); + + macro->match.length = strlen(macro->match.pattern); + macro->match.flags = GIT_ATTR_FNMATCH_MACRO; + + error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); + + if (!error) + error = git_attr_cache__insert_macro(repo, macro); + + if (error < 0) + git_attr_rule__free(macro); + + return error; +} + +bool git_attr_cache__is_cached( + git_repository *repo, git_attr_file_source source, const char *path) +{ + git_buf cache_key = GIT_BUF_INIT; + git_strmap *files = git_repository_attr_cache(repo)->files; + const char *workdir = git_repository_workdir(repo); + bool rval; + + if (workdir && git__prefixcmp(path, workdir) == 0) + path += strlen(workdir); + if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0) + return false; + + rval = git_strmap_exists(files, git_buf_cstr(&cache_key)); + + git_buf_free(&cache_key); + + return rval; +} + +static int load_attr_file( + const char **data, + git_attr_file_stat_sig *sig, + const char *filename) +{ + int error; + git_buf content = GIT_BUF_INIT; + struct stat st; + + if (p_stat(filename, &st) < 0) + return GIT_ENOTFOUND; + + if (sig != NULL && + (git_time_t)st.st_mtime == sig->seconds && + (git_off_t)st.st_size == sig->size && + (unsigned int)st.st_ino == sig->ino) + return GIT_ENOTFOUND; + + error = git_futils_readbuffer_updated(&content, filename, NULL, NULL); + if (error < 0) + return error; + + if (sig != NULL) { + sig->seconds = (git_time_t)st.st_mtime; + sig->size = (git_off_t)st.st_size; + sig->ino = (unsigned int)st.st_ino; + } + + *data = git_buf_detach(&content); + + return 0; +} + +static int load_attr_blob_from_index( + const char **content, + git_blob **blob, + git_repository *repo, + const git_oid *old_oid, + const char *relfile) +{ + int error; + git_index *index; + git_index_entry *entry; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_index_find(index, relfile)) < 0) + return error; + + entry = git_index_get(index, error); + + if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0) + return GIT_ENOTFOUND; + + if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 0) + return error; + + *content = git_blob_rawcontent(*blob); + return 0; +} + +static int load_attr_from_cache( + git_attr_file **file, + git_attr_cache *cache, + git_attr_file_source source, + const char *relative_path) +{ + git_buf cache_key = GIT_BUF_INIT; + khiter_t cache_pos; + + *file = NULL; + + if (!cache || !cache->files) + return 0; + + if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0) + return -1; + + cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr); + + git_buf_free(&cache_key); + + if (git_strmap_valid_index(cache->files, cache_pos)) + *file = git_strmap_value_at(cache->files, cache_pos); + + return 0; +} + +int git_attr_cache__internal_file( + git_repository *repo, + const char *filename, + git_attr_file **file) +{ + int error = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + khiter_t cache_pos = git_strmap_lookup_index(cache->files, filename); + + if (git_strmap_valid_index(cache->files, cache_pos)) { + *file = git_strmap_value_at(cache->files, cache_pos); + return 0; + } + + if (git_attr_file__new(file, 0, filename, &cache->pool) < 0) + return -1; + + git_strmap_insert(cache->files, (*file)->key + 2, *file, error); + if (error > 0) + error = 0; + + return error; +} + +int git_attr_cache__push_file( + git_repository *repo, + const char *base, + const char *filename, + git_attr_file_source source, + git_attr_file_parser parse, + git_vector *stack) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + const char *workdir = git_repository_workdir(repo); + const char *relfile, *content = NULL; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file *file = NULL; + git_blob *blob = NULL; + git_attr_file_stat_sig st; + + assert(filename && stack); + + /* join base and path as needed */ + if (base != NULL && git_path_root(filename) < 0) { + if (git_buf_joinpath(&path, base, filename) < 0) + return -1; + filename = path.ptr; + } + + relfile = filename; + if (workdir && git__prefixcmp(relfile, workdir) == 0) + relfile += strlen(workdir); + + /* check cache */ + if (load_attr_from_cache(&file, cache, source, relfile) < 0) + return -1; + + /* if not in cache, load data, parse, and cache */ + + if (source == GIT_ATTR_FILE_FROM_FILE) { + if (file) + memcpy(&st, &file->cache_data.st, sizeof(st)); + else + memset(&st, 0, sizeof(st)); + + error = load_attr_file(&content, &st, filename); + } else { + error = load_attr_blob_from_index(&content, &blob, + repo, file ? &file->cache_data.oid : NULL, relfile); + } + + if (error) { + /* not finding a file is not an error for this function */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + goto finish; + } + + /* if we got here, we have to parse and/or reparse the file */ + if (file) + git_attr_file__clear_rules(file); + else { + error = git_attr_file__new(&file, source, relfile, &cache->pool); + if (error < 0) + goto finish; + } + + if (parse && (error = parse(repo, content, file)) < 0) + goto finish; + + git_strmap_insert(cache->files, file->key, file, error); + if (error > 0) + error = 0; + + /* remember "cache buster" file signature */ + if (blob) + git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); + else + memcpy(&file->cache_data.st, &st, sizeof(st)); + +finish: + /* push file onto vector if we found one*/ + if (!error && file != NULL) + error = git_vector_insert(stack, file); + + if (error != 0) + git_attr_file__free(file); + + if (blob) + git_blob_free(blob); + else + git__free((void *)content); + + git_buf_free(&path); + + return error; +} + +#define push_attr_file(R,S,B,F) \ + git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S)) + +typedef struct { + git_repository *repo; + uint32_t flags; + const char *workdir; + git_index *index; + git_vector *files; +} attr_walk_up_info; + +int git_attr_cache__decide_sources( + uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs) +{ + int count = 0; + + switch (flags & 0x03) { + case GIT_ATTR_CHECK_FILE_THEN_INDEX: + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + case GIT_ATTR_CHECK_INDEX_THEN_FILE: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + break; + case GIT_ATTR_CHECK_INDEX_ONLY: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + } + + return count; +} + +static int push_one_attr(void *ref, git_buf *path) +{ + int error = 0, n_src, i; + attr_walk_up_info *info = (attr_walk_up_info *)ref; + git_attr_file_source src[2]; + + n_src = git_attr_cache__decide_sources( + info->flags, info->workdir != NULL, info->index != NULL, src); + + for (i = 0; !error && i < n_src; ++i) + error = git_attr_cache__push_file( + info->repo, path->ptr, GIT_ATTR_FILE, src[i], + git_attr_file__parse_buffer, info->files); + + return error; +} + +static int collect_attr_files( + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files) +{ + int error; + git_buf dir = GIT_BUF_INIT; + const char *workdir = git_repository_workdir(repo); + attr_walk_up_info info; + + if (git_attr_cache__init(repo) < 0 || + git_vector_init(files, 4, NULL) < 0) + return -1; + + /* Resolve path in a non-bare repo */ + if (workdir != NULL) + error = git_path_find_dir(&dir, path, workdir); + else + error = git_path_dirname_r(&dir, path); + if (error < 0) + goto cleanup; + + /* in precendence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + error = push_attr_file( + repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO); + if (error < 0) + goto cleanup; + + info.repo = repo; + info.flags = flags; + info.workdir = workdir; + if (git_repository_index__weakptr(&info.index, repo) < 0) + giterr_clear(); /* no error even if there is no index */ + info.files = files; + + error = git_path_walk_up(&dir, workdir, push_one_attr, &info); + if (error < 0) + goto cleanup; + + if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { + error = push_attr_file( + repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; + } + + if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { + error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); + if (!error) + error = push_attr_file(repo, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + error = 0; + } + + cleanup: + if (error < 0) + git_vector_free(files); + git_buf_free(&dir); + + return error; +} + + +int git_attr_cache__init(git_repository *repo) +{ + int ret; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_config *cfg; + + if (cache->initialized) + return 0; + + /* cache config settings for attributes and ignores */ + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + giterr_clear(); + + /* allocate hashtable for attribute and ignore file contents */ + if (cache->files == NULL) { + cache->files = git_strmap_alloc(); + GITERR_CHECK_ALLOC(cache->files); + } + + /* allocate hashtable for attribute macros */ + if (cache->macros == NULL) { + cache->macros = git_strmap_alloc(); + GITERR_CHECK_ALLOC(cache->macros); + } + + /* allocate string pool */ + if (git_pool_init(&cache->pool, 1, 0) < 0) + return -1; + + cache->initialized = 1; + + /* insert default macros */ + return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); +} + +void git_attr_cache_flush( + git_repository *repo) +{ + git_attr_cache *cache; + + if (!repo) + return; + + cache = git_repository_attr_cache(repo); + + if (cache->files != NULL) { + git_attr_file *file; + + git_strmap_foreach_value(cache->files, file, { + git_attr_file__free(file); + }); + + git_strmap_free(cache->files); + } + + if (cache->macros != NULL) { + git_attr_rule *rule; + + git_strmap_foreach_value(cache->macros, rule, { + git_attr_rule__free(rule); + }); + + git_strmap_free(cache->macros); + } + + git_pool_clear(&cache->pool); + + cache->initialized = 0; +} + +int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) +{ + git_strmap *macros = git_repository_attr_cache(repo)->macros; + int error; + + /* TODO: generate warning log if (macro->assigns.length == 0) */ + if (macro->assigns.length == 0) + return 0; + + git_strmap_insert(macros, macro->match.pattern, macro, error); + return (error < 0) ? -1 : 0; +} + +git_attr_rule *git_attr_cache__lookup_macro( + git_repository *repo, const char *name) +{ + git_strmap *macros = git_repository_attr_cache(repo)->macros; + khiter_t pos; + + pos = git_strmap_lookup_index(macros, name); + + if (!git_strmap_valid_index(macros, pos)) + return NULL; + + return (git_attr_rule *)git_strmap_value_at(macros, pos); +} + diff --git a/src/attr.h b/src/attr.h new file mode 100644 index 000000000..a35b1160f --- /dev/null +++ b/src/attr.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_attr_h__ +#define INCLUDE_attr_h__ + +#include "attr_file.h" +#include "strmap.h" + +#define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_IGNORE_CONFIG "core.excludesfile" + +typedef struct { + int initialized; + git_pool pool; + git_strmap *files; /* hash path to git_attr_file of rules */ + git_strmap *macros; /* hash name to vector */ + const char *cfg_attr_file; /* cached value of core.attributesfile */ + const char *cfg_excl_file; /* cached value of core.excludesfile */ +} git_attr_cache; + +typedef int (*git_attr_file_parser)( + git_repository *, const char *, git_attr_file *); + +extern int git_attr_cache__init(git_repository *repo); + +extern int git_attr_cache__insert_macro( + git_repository *repo, git_attr_rule *macro); + +extern git_attr_rule *git_attr_cache__lookup_macro( + git_repository *repo, const char *name); + +extern int git_attr_cache__push_file( + git_repository *repo, + const char *base, + const char *filename, + git_attr_file_source source, + git_attr_file_parser parse, + git_vector *stack); + +extern int git_attr_cache__internal_file( + git_repository *repo, + const char *key, + git_attr_file **file_ptr); + +/* returns true if path is in cache */ +extern bool git_attr_cache__is_cached( + git_repository *repo, git_attr_file_source source, const char *path); + +extern int git_attr_cache__decide_sources( + uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs); + +#endif diff --git a/src/attr_file.c b/src/attr_file.c new file mode 100644 index 000000000..ca2f8fb58 --- /dev/null +++ b/src/attr_file.c @@ -0,0 +1,609 @@ +#include "common.h" +#include "repository.h" +#include "filebuf.h" +#include "git2/blob.h" +#include "git2/tree.h" +#include + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; +const char *git_attr__unset = "[internal]__UNSET__"; + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); +static void git_attr_rule__clear(git_attr_rule *rule); + +int git_attr_file__new( + git_attr_file **attrs_ptr, + git_attr_file_source from, + const char *path, + git_pool *pool) +{ + git_attr_file *attrs = NULL; + + attrs = git__calloc(1, sizeof(git_attr_file)); + GITERR_CHECK_ALLOC(attrs); + + if (pool) + attrs->pool = pool; + else { + attrs->pool = git__calloc(1, sizeof(git_pool)); + if (!attrs->pool || git_pool_init(attrs->pool, 1, 0) < 0) + goto fail; + attrs->pool_is_allocated = true; + } + + if (path) { + size_t len = strlen(path); + + attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3); + GITERR_CHECK_ALLOC(attrs->key); + + attrs->key[0] = '0' + from; + attrs->key[1] = '#'; + memcpy(&attrs->key[2], path, len); + attrs->key[len + 2] = '\0'; + } + + if (git_vector_init(&attrs->rules, 4, NULL) < 0) + goto fail; + + *attrs_ptr = attrs; + return 0; + +fail: + git_attr_file__free(attrs); + attrs_ptr = NULL; + return -1; +} + +int git_attr_file__parse_buffer( + git_repository *repo, const char *buffer, git_attr_file *attrs) +{ + int error = 0; + const char *scan = NULL; + char *context = NULL; + git_attr_rule *rule = NULL; + + assert(buffer && attrs); + + scan = buffer; + + /* if subdir file path, convert context for file paths */ + if (attrs->key && git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0) { + context = attrs->key + 2; + context[strlen(context) - strlen(GIT_ATTR_FILE)] = '\0'; + } + + while (!error && *scan) { + /* allocate rule if needed */ + if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = -1; + break; + } + + /* parse the next "pattern attr attr attr" line */ + if (!(error = git_attr_fnmatch__parse( + &rule->match, attrs->pool, context, &scan)) && + !(error = git_attr_assignment__parse( + repo, attrs->pool, &rule->assigns, &scan))) + { + if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) + /* should generate error/warning if this is coming from any + * file other than .gitattributes at repo root. + */ + error = git_attr_cache__insert_macro(repo, rule); + else + error = git_vector_insert(&attrs->rules, rule); + } + + /* if the rule wasn't a pattern, on to the next */ + if (error < 0) { + git_attr_rule__clear(rule); /* reset rule contents */ + if (error == GIT_ENOTFOUND) + error = 0; + } else { + rule = NULL; /* vector now "owns" the rule */ + } + } + + git_attr_rule__free(rule); + + /* restore file path used for context */ + if (context) + context[strlen(context)] = '.'; /* first char of GIT_ATTR_FILE */ + + return error; +} + +int git_attr_file__new_and_load( + git_attr_file **attrs_ptr, + const char *path) +{ + int error; + git_buf content = GIT_BUF_INIT; + + if ((error = git_attr_file__new(attrs_ptr, 0, path, NULL)) < 0) + return error; + + if (!(error = git_futils_readbuffer(&content, path))) + error = git_attr_file__parse_buffer( + NULL, git_buf_cstr(&content), *attrs_ptr); + + git_buf_free(&content); + + if (error) { + git_attr_file__free(*attrs_ptr); + *attrs_ptr = NULL; + } + + return error; +} + +void git_attr_file__clear_rules(git_attr_file *file) +{ + unsigned int i; + git_attr_rule *rule; + + git_vector_foreach(&file->rules, i, rule) + git_attr_rule__free(rule); + + git_vector_free(&file->rules); +} + +void git_attr_file__free(git_attr_file *file) +{ + if (!file) + return; + + git_attr_file__clear_rules(file); + + if (file->pool_is_allocated) { + git_pool_clear(file->pool); + git__free(file->pool); + } + file->pool = NULL; + + git__free(file); +} + +uint32_t git_attr_file__name_hash(const char *name) +{ + uint32_t h = 5381; + int c; + assert(name); + while ((c = (int)*name++) != 0) + h = ((h << 5) + h) + c; + return h; +} + + +int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value) +{ + unsigned int i; + git_attr_name name; + git_attr_rule *rule; + + *value = NULL; + + name.name = attr; + name.name_hash = git_attr_file__name_hash(attr); + + git_attr_file__foreach_matching_rule(file, path, i, rule) { + int pos = git_vector_bsearch(&rule->assigns, &name); + + if (pos >= 0) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return 0; +} + + +bool git_attr_fnmatch__match( + git_attr_fnmatch *match, + const git_attr_path *path) +{ + int fnm; + + if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) + return false; + + if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) + fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); + else if (path->is_dir) + fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR); + else + fnm = p_fnmatch(match->pattern, path->basename, 0); + + return (fnm == FNM_NOMATCH) ? false : true; +} + +bool git_attr_rule__match( + git_attr_rule *rule, + const git_attr_path *path) +{ + bool matched = git_attr_fnmatch__match(&rule->match, path); + + if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) + matched = !matched; + + return matched; +} + + +git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name) +{ + int pos; + git_attr_name key; + key.name = name; + key.name_hash = git_attr_file__name_hash(name); + + pos = git_vector_bsearch(&rule->assigns, &key); + + return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; +} + +int git_attr_path__init( + git_attr_path *info, const char *path, const char *base) +{ + /* build full path as best we can */ + git_buf_init(&info->full, 0); + + if (base != NULL && git_path_root(path) < 0) { + if (git_buf_joinpath(&info->full, base, path) < 0) + return -1; + info->path = info->full.ptr + strlen(base); + } else { + if (git_buf_sets(&info->full, path) < 0) + return -1; + info->path = info->full.ptr; + } + + /* remove trailing slashes */ + while (info->full.size > 0) { + if (info->full.ptr[info->full.size - 1] != '/') + break; + info->full.size--; + } + info->full.ptr[info->full.size] = '\0'; + + /* skip leading slashes in path */ + while (*info->path == '/') + info->path++; + + /* find trailing basename component */ + info->basename = strrchr(info->path, '/'); + if (info->basename) + info->basename++; + if (!info->basename || !*info->basename) + info->basename = info->path; + + info->is_dir = (int)git_path_isdir(info->full.ptr); + + return 0; +} + +void git_attr_path__free(git_attr_path *info) +{ + git_buf_free(&info->full); + info->path = NULL; + info->basename = NULL; +} + + +/* + * From gitattributes(5): + * + * Patterns have the following format: + * + * - A blank line matches no files, so it can serve as a separator for + * readability. + * + * - A line starting with # serves as a comment. + * + * - An optional prefix ! which negates the pattern; any matching file + * excluded by a previous pattern will become included again. If a negated + * pattern matches, this will override lower precedence patterns sources. + * + * - If the pattern ends with a slash, it is removed for the purpose of the + * following description, but it would only find a match with a directory. In + * other words, foo/ will match a directory foo and paths underneath it, but + * will not match a regular file or a symbolic link foo (this is consistent + * with the way how pathspec works in general in git). + * + * - If the pattern does not contain a slash /, git treats it as a shell glob + * pattern and checks for a match against the pathname without leading + * directories. + * + * - Otherwise, git treats the pattern as a shell glob suitable for consumption + * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will + * not match a / in the pathname. For example, "Documentation/\*.html" matches + * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading + * slash matches the beginning of the pathname; for example, "/\*.c" matches + * "cat-file.c" but not "mozilla-sha1/sha1.c". + */ + +/* + * This will return 0 if the spec was filled out, + * GIT_ENOTFOUND if the fnmatch does not require matching, or + * another error code there was an actual problem. + */ +int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base) +{ + const char *pattern, *scan; + int slash_count, allow_space; + + assert(spec && base && *base); + + spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE); + allow_space = (spec->flags != 0); + + pattern = *base; + + while (git__isspace(*pattern)) pattern++; + if (!*pattern || *pattern == '#') { + *base = git__next_line(pattern); + return GIT_ENOTFOUND; + } + + if (*pattern == '[') { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } + /* else a character range like [a-e]* which is accepted */ + } + + if (*pattern == '!') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + + slash_count = 0; + for (scan = pattern; *scan != '\0'; ++scan) { + /* scan until (non-escaped) white space */ + if (git__isspace(*scan) && *(scan - 1) != '\\') { + if (!allow_space || (*scan != ' ' && *scan != '\t')) + break; + } + + if (*scan == '/') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; + slash_count++; + if (pattern == scan) + pattern++; + } + /* remember if we see an unescaped wildcard in pattern */ + else if (git__iswildcard(*scan) && + (scan == pattern || (*(scan - 1) != '\\'))) + spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; + } + + *base = scan; + + spec->length = scan - pattern; + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; + if (--slash_count <= 0) + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; + } + + if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 && + source != NULL && git_path_root(pattern) < 0) + { + size_t sourcelen = strlen(source); + /* given an unrooted fullpath match from a file inside a repo, + * prefix the pattern with the relative directory of the source file + */ + spec->pattern = git_pool_malloc( + pool, (uint32_t)(sourcelen + spec->length + 1)); + if (spec->pattern) { + memcpy(spec->pattern, source, sourcelen); + memcpy(spec->pattern + sourcelen, pattern, spec->length); + spec->length += sourcelen; + spec->pattern[spec->length] = '\0'; + } + } else { + spec->pattern = git_pool_strndup(pool, pattern, spec->length); + } + + if (!spec->pattern) { + *base = git__next_line(pattern); + return -1; + } else { + /* strip '\' that might have be used for internal whitespace */ + char *to = spec->pattern; + for (scan = spec->pattern; *scan; to++, scan++) { + if (*scan == '\\') + scan++; /* skip '\' but include next char */ + if (to != scan) + *to = *scan; + } + if (to != scan) { + *to = '\0'; + spec->length = (to - spec->pattern); + } + } + + return 0; +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) +{ + const git_attr_name *a = a_raw; + const git_attr_name *b = b_raw; + + if (b->name_hash < a->name_hash) + return 1; + else if (b->name_hash > a->name_hash) + return -1; + else + return strcmp(b->name, a->name); +} + +static void git_attr_assignment__free(git_attr_assignment *assign) +{ + /* name and value are stored in a git_pool associated with the + * git_attr_file, so they do not need to be freed here + */ + assign->name = NULL; + assign->value = NULL; + git__free(assign); +} + +static int merge_assignments(void **old_raw, void *new_raw) +{ + git_attr_assignment **old = (git_attr_assignment **)old_raw; + git_attr_assignment *new = (git_attr_assignment *)new_raw; + + GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); + *old = new; + return GIT_EEXISTS; +} + +int git_attr_assignment__parse( + git_repository *repo, + git_pool *pool, + git_vector *assigns, + const char **base) +{ + int error; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + assert(assigns && !assigns->length); + + assigns->_cmp = sort_by_hash_and_name; + + while (*scan && *scan != '\n') { + const char *name_start, *value_start; + + /* skip leading blanks */ + while (git__isspace(*scan) && *scan != '\n') scan++; + + /* allocate assign if needed */ + if (!assign) { + assign = git__calloc(1, sizeof(git_attr_assignment)); + GITERR_CHECK_ALLOC(assign); + GIT_REFCOUNT_INC(assign); + } + + assign->name_hash = 5381; + assign->value = git_attr__true; + + /* look for magic name prefixes */ + if (*scan == '-') { + assign->value = git_attr__false; + scan++; + } else if (*scan == '!') { + assign->value = git_attr__unset; /* explicit unspecified state */ + scan++; + } else if (*scan == '#') /* comment rest of line */ + break; + + /* find the name */ + name_start = scan; + while (*scan && !git__isspace(*scan) && *scan != '=') { + assign->name_hash = + ((assign->name_hash << 5) + assign->name_hash) + *scan; + scan++; + } + if (scan == name_start) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !git__isspace(*scan)) scan++; + continue; + } + + /* allocate permanent storage for name */ + assign->name = git_pool_strndup(pool, name_start, scan - name_start); + GITERR_CHECK_ALLOC(assign->name); + + /* if there is an equals sign, find the value */ + if (*scan == '=') { + for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); + + /* if we found a value, allocate permanent storage for it */ + if (scan > value_start) { + assign->value = git_pool_strndup(pool, value_start, scan - value_start); + GITERR_CHECK_ALLOC(assign->value); + } + } + + /* expand macros (if given a repo with a macro cache) */ + if (repo != NULL && assign->value == git_attr__true) { + git_attr_rule *macro = + git_attr_cache__lookup_macro(repo, assign->name); + + if (macro != NULL) { + unsigned int i; + git_attr_assignment *massign; + + git_vector_foreach(¯o->assigns, i, massign) { + GIT_REFCOUNT_INC(massign); + + error = git_vector_insert_sorted( + assigns, massign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + } + } + } + + /* insert allocated assign into vector */ + error = git_vector_insert_sorted(assigns, assign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (assign != NULL) + git_attr_assignment__free(assign); + + *base = git__next_line(scan); + + return (assigns->length == 0) ? GIT_ENOTFOUND : 0; +} + +static void git_attr_rule__clear(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return; + + if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + git_vector_free(&rule->assigns); + } + + /* match.pattern is stored in a git_pool, so no need to free */ + rule->match.pattern = NULL; + rule->match.length = 0; +} + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + diff --git a/src/attr_file.h b/src/attr_file.h new file mode 100644 index 000000000..7939f838a --- /dev/null +++ b/src/attr_file.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "git2/attr.h" +#include "vector.h" +#include "pool.h" +#include "buffer.h" + +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_INREPO "info/attributes" +#define GIT_ATTR_FILE_SYSTEM "gitattributes" + +#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) +#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) +#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) +#define GIT_ATTR_FNMATCH_MACRO (1U << 3) +#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) +#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) +#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) + +typedef struct { + char *pattern; + size_t length; + unsigned int flags; +} git_attr_fnmatch; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of */ +} git_attr_rule; + +typedef struct { + git_refcount unused; + const char *name; + uint32_t name_hash; +} git_attr_name; + +typedef struct { + git_refcount rc; /* for macros */ + char *name; + uint32_t name_hash; + const char *value; +} git_attr_assignment; + +typedef struct { + git_time_t seconds; + git_off_t size; + unsigned int ino; +} git_attr_file_stat_sig; + +typedef struct { + char *key; /* cache "source#path" this was loaded from */ + git_vector rules; /* vector of or */ + git_pool *pool; + bool pool_is_allocated; + union { + git_oid oid; + git_attr_file_stat_sig st; + } cache_data; +} git_attr_file; + +typedef struct { + git_buf full; + const char *path; + const char *basename; + int is_dir; +} git_attr_path; + +typedef enum { + GIT_ATTR_FILE_FROM_FILE = 0, + GIT_ATTR_FILE_FROM_INDEX = 1 +} git_attr_file_source; + +/* + * git_attr_file API + */ + +extern int git_attr_file__new( + git_attr_file **attrs_ptr, git_attr_file_source src, const char *path, git_pool *pool); + +extern int git_attr_file__new_and_load( + git_attr_file **attrs_ptr, const char *path); + +extern void git_attr_file__free(git_attr_file *file); + +extern void git_attr_file__clear_rules(git_attr_file *file); + +extern int git_attr_file__parse_buffer( + git_repository *repo, const char *buf, git_attr_file *file); + +extern int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match((rule), (path))) + +extern uint32_t git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base); + +extern bool git_attr_fnmatch__match( + git_attr_fnmatch *rule, + const git_attr_path *path); + +extern void git_attr_rule__free(git_attr_rule *rule); + +extern bool git_attr_rule__match( + git_attr_rule *rule, + const git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +extern int git_attr_path__init( + git_attr_path *info, const char *path, const char *base); + +extern void git_attr_path__free(git_attr_path *info); + +extern int git_attr_assignment__parse( + git_repository *repo, /* needed to expand macros */ + git_pool *pool, + git_vector *assigns, + const char **scan); + +#endif diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c deleted file mode 100644 index 111abf7d3..000000000 --- a/src/backends/hiredis.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "git2/object.h" -#include "hash.h" -#include "odb.h" - -#include "git2/odb_backend.h" - -#ifdef GIT2_HIREDIS_BACKEND - -#include - -typedef struct { - git_odb_backend parent; - - redisContext *db; -} hiredis_backend; - -int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(len_p && type_p && _backend && oid); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, - "type", "size"); - - if (reply && reply->type == REDIS_REPLY_ARRAY) { - if (reply->element[0]->type != REDIS_REPLY_NIL && - reply->element[0]->type != REDIS_REPLY_NIL) { - *type_p = (git_otype) atoi(reply->element[0]->str); - *len_p = (size_t) atoi(reply->element[1]->str); - error = GIT_SUCCESS; - } else { - error = GIT_ENOTFOUND; - } - } else { - error = GIT_ERROR; - } - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read header"); -} - -int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(data_p && len_p && type_p && _backend && oid); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, - "type", "size", "data"); - - if (reply && reply->type == REDIS_REPLY_ARRAY) { - if (reply->element[0]->type != REDIS_REPLY_NIL && - reply->element[1]->type != REDIS_REPLY_NIL && - reply->element[2]->type != REDIS_REPLY_NIL) { - *type_p = (git_otype) atoi(reply->element[0]->str); - *len_p = (size_t) atoi(reply->element[1]->str); - *data_p = git__malloc(*len_p); - if (*data_p == NULL) { - error = GIT_ENOMEM; - } else { - memcpy(*data_p, reply->element[2]->str, *len_p); - error = GIT_SUCCESS; - } - } else { - error = GIT_ENOTFOUND; - } - } else { - error = GIT_ERROR; - } - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read backend"); -} - -int hiredis_backend__read_prefix(git_oid *out_oid, void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, - const git_oid *short_oid, unsigned int len) { - if (len >= GIT_OID_HEXSZ) { - /* Just match the full identifier */ - int error = hiredis_backend__read(data_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) - git_oid_cpy(out_oid, short_oid); - - return error; - } else if (len < GIT_OID_HEXSZ) { - /* TODO */ - return git__throw(GIT_ENOTIMPLEMENTED, "Hiredis backend cannot search objects from short oid"); - } -} - -int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int found; - redisReply *reply; - - assert(_backend && oid); - - backend = (hiredis_backend *) _backend; - found = 0; - - reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); - if (reply && reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) - found = 1; - - - freeReplyObject(reply); - return found; -} - -int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(id && _backend && data); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - if ((error = git_odb_hash(id, data, len, type)) < 0) - return git__rethrow(error, "Failed to write backend"); - - reply = redisCommand(backend->db, "HMSET %b " - "type %d " - "size %d " - "data %b ", id->id, GIT_OID_RAWSZ, - (int) type, len, data, len); - - error = (reply == NULL || reply->type == REDIS_REPLY_ERROR) ? GIT_ERROR : GIT_SUCCESS; - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write backend"); -} - -void hiredis_backend__free(git_odb_backend *_backend) { - hiredis_backend *backend; - assert(_backend); - backend = (hiredis_backend *) _backend; - - redisFree(backend->db); - - free(backend); -} - -int git_odb_backend_hiredis(git_odb_backend **backend_out, const char *host, int port) { - hiredis_backend *backend; - - backend = git__calloc(1, sizeof (hiredis_backend)); - if (backend == NULL) - return GIT_ENOMEM; - - - backend->db = redisConnect(host, port); - if (backend->db->err) - goto cleanup; - - backend->parent.read = &hiredis_backend__read; - backend->parent.read_prefix = &hiredis_backend__read_prefix; - backend->parent.read_header = &hiredis_backend__read_header; - backend->parent.write = &hiredis_backend__write; - backend->parent.exists = &hiredis_backend__exists; - backend->parent.free = &hiredis_backend__free; - - *backend_out = (git_odb_backend *) backend; - - return GIT_SUCCESS; -cleanup: - free(backend); - return git__throw(GIT_ERROR, "Failed to get ODB backend"); -} - -#else - -int git_odb_backend_hiredis(git_odb_backend ** GIT_UNUSED(backend_out), - const char *GIT_UNUSED(host), int GIT_UNUSED(port)) { - GIT_UNUSED_ARG(backend_out); - GIT_UNUSED_ARG(host); - GIT_UNUSED_ARG(port); - return git__throw(GIT_ENOTIMPLEMENTED, "Failed to get ODB backend. Feature not yet implemented"); -} - - -#endif /* HAVE_HIREDIS */ diff --git a/src/backends/sqlite.c b/src/backends/sqlite.c deleted file mode 100644 index 8626349ec..000000000 --- a/src/backends/sqlite.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "git2/object.h" -#include "hash.h" -#include "odb.h" - -#include "git2/odb_backend.h" - -#ifdef GIT2_SQLITE_BACKEND - -#include - -#define GIT2_TABLE_NAME "git2_odb" - -typedef struct { - git_odb_backend parent; - sqlite3 *db; - sqlite3_stmt *st_read; - sqlite3_stmt *st_write; - sqlite3_stmt *st_read_header; -} sqlite_backend; - -int sqlite_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int error; - - assert(len_p && type_p && _backend && oid); - - backend = (sqlite_backend *)_backend; - error = GIT_ERROR; - - if (sqlite3_bind_text(backend->st_read_header, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read_header) == SQLITE_ROW) { - *type_p = (git_otype)sqlite3_column_int(backend->st_read_header, 0); - *len_p = (size_t)sqlite3_column_int(backend->st_read_header, 1); - assert(sqlite3_step(backend->st_read_header) == SQLITE_DONE); - error = GIT_SUCCESS; - } else { - error = GIT_ENOTFOUND; - } - } - - sqlite3_reset(backend->st_read_header); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to read header"); -} - - -int sqlite_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int error; - - assert(data_p && len_p && type_p && _backend && oid); - - backend = (sqlite_backend *)_backend; - error = GIT_ERROR; - - if (sqlite3_bind_text(backend->st_read, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read) == SQLITE_ROW) { - *type_p = (git_otype)sqlite3_column_int(backend->st_read, 0); - *len_p = (size_t)sqlite3_column_int(backend->st_read, 1); - *data_p = git__malloc(*len_p); - - if (*data_p == NULL) { - error = GIT_ENOMEM; - } else { - memcpy(*data_p, sqlite3_column_blob(backend->st_read, 2), *len_p); - error = GIT_SUCCESS; - } - - assert(sqlite3_step(backend->st_read) == SQLITE_DONE); - } else { - error = GIT_ENOTFOUND; - } - } - - sqlite3_reset(backend->st_read); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to read"); -} - -int sqlite_backend__read_prefix(git_oid *out_oid, void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, - const git_oid *short_oid, unsigned int len) { - if (len >= GIT_OID_HEXSZ) { - /* Just match the full identifier */ - int error = sqlite_backend__read(data_p, len_p, type_p, _backend, short_oid); - if (error == GIT_SUCCESS) - git_oid_cpy(out_oid, short_oid); - - return error; - } else if (len < GIT_OID_HEXSZ) { - /* TODO */ - return git__throw(GIT_ENOTIMPLEMENTED, "Sqlite backend cannot search objects from short oid"); - } -} - -int sqlite_backend__exists(git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int found; - - assert(_backend && oid); - - backend = (sqlite_backend *)_backend; - found = 0; - - if (sqlite3_bind_text(backend->st_read_header, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read_header) == SQLITE_ROW) { - found = 1; - assert(sqlite3_step(backend->st_read_header) == SQLITE_DONE); - } - } - - sqlite3_reset(backend->st_read_header); - return found; -} - - -int sqlite_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) -{ - int error; - sqlite_backend *backend; - - assert(id && _backend && data); - - backend = (sqlite_backend *)_backend; - - if ((error = git_odb_hash(id, data, len, type)) < 0) - return git__rethrow(error, "SQLite backend: Failed to write"); - - error = SQLITE_ERROR; - - if (sqlite3_bind_text(backend->st_write, 1, (char *)id->id, 20, SQLITE_TRANSIENT) == SQLITE_OK && - sqlite3_bind_int(backend->st_write, 2, (int)type) == SQLITE_OK && - sqlite3_bind_int(backend->st_write, 3, len) == SQLITE_OK && - sqlite3_bind_blob(backend->st_write, 4, data, len, SQLITE_TRANSIENT) == SQLITE_OK) { - error = sqlite3_step(backend->st_write); - } - - sqlite3_reset(backend->st_write); - return (error == SQLITE_DONE) ? GIT_SUCCESS : git__throw(GIT_ERROR, "SQLite backend: Failed to write"); -} - - -void sqlite_backend__free(git_odb_backend *_backend) -{ - sqlite_backend *backend; - assert(_backend); - backend = (sqlite_backend *)_backend; - - sqlite3_finalize(backend->st_read); - sqlite3_finalize(backend->st_read_header); - sqlite3_finalize(backend->st_write); - sqlite3_close(backend->db); - - free(backend); -} - -static int create_table(sqlite3 *db) -{ - static const char *sql_creat = - "CREATE TABLE '" GIT2_TABLE_NAME "' (" - "'oid' CHARACTER(20) PRIMARY KEY NOT NULL," - "'type' INTEGER NOT NULL," - "'size' INTEGER NOT NULL," - "'data' BLOB);"; - - if (sqlite3_exec(db, sql_creat, NULL, NULL, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to create table"); - - return GIT_SUCCESS; -} - -static int init_db(sqlite3 *db) -{ - static const char *sql_check = - "SELECT name FROM sqlite_master WHERE type='table' AND name='" GIT2_TABLE_NAME "';"; - - sqlite3_stmt *st_check; - int error; - - if (sqlite3_prepare_v2(db, sql_check, -1, &st_check, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize database"); - - switch (sqlite3_step(st_check)) { - case SQLITE_DONE: - /* the table was not found */ - error = create_table(db); - break; - - case SQLITE_ROW: - /* the table was found */ - error = GIT_SUCCESS; - break; - - default: - error = GIT_ERROR; - break; - } - - sqlite3_finalize(st_check); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to initialize database"); -} - -static int init_statements(sqlite_backend *backend) -{ - static const char *sql_read = - "SELECT type, size, data FROM '" GIT2_TABLE_NAME "' WHERE oid = ?;"; - - static const char *sql_read_header = - "SELECT type, size FROM '" GIT2_TABLE_NAME "' WHERE oid = ?;"; - - static const char *sql_write = - "INSERT OR IGNORE INTO '" GIT2_TABLE_NAME "' VALUES (?, ?, ?, ?);"; - - if (sqlite3_prepare_v2(backend->db, sql_read, -1, &backend->st_read, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - if (sqlite3_prepare_v2(backend->db, sql_read_header, -1, &backend->st_read_header, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - if (sqlite3_prepare_v2(backend->db, sql_write, -1, &backend->st_write, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - return GIT_SUCCESS; -} - -int git_odb_backend_sqlite(git_odb_backend **backend_out, const char *sqlite_db) -{ - sqlite_backend *backend; - int error; - - backend = git__calloc(1, sizeof(sqlite_backend)); - if (backend == NULL) - return GIT_ENOMEM; - - if (sqlite3_open(sqlite_db, &backend->db) != SQLITE_OK) - goto cleanup; - - error = init_db(backend->db); - if (error < 0) - goto cleanup; - - error = init_statements(backend); - if (error < 0) - goto cleanup; - - backend->parent.read = &sqlite_backend__read; - backend->parent.read_prefix = &sqlite_backend__read_prefix; - backend->parent.read_header = &sqlite_backend__read_header; - backend->parent.write = &sqlite_backend__write; - backend->parent.exists = &sqlite_backend__exists; - backend->parent.free = &sqlite_backend__free; - - *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; - -cleanup: - sqlite_backend__free((git_odb_backend *)backend); - return git__throw(GIT_ERROR, "SQLite backend: Failed to get ODB backend"); -} - -#else - -int git_odb_backend_sqlite(git_odb_backend **GIT_UNUSED(backend_out), const char *GIT_UNUSED(sqlite_db)) -{ - GIT_UNUSED_ARG(backend_out); - GIT_UNUSED_ARG(sqlite_db); - return git__throw(GIT_ENOTIMPLEMENTED, "SQLite backend: Failed to get ODB backend. Operation not yet implemented"); -} - -#endif /* HAVE_SQLITE3 */ diff --git a/src/blob.c b/src/blob.c index 6ab58d6b2..699adec6b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/common.h" @@ -29,6 +11,7 @@ #include "common.h" #include "blob.h" +#include "filter.h" const void *git_blob_rawcontent(git_blob *blob) { @@ -36,16 +19,22 @@ const void *git_blob_rawcontent(git_blob *blob) return blob->odb_object->raw.data; } -int git_blob_rawsize(git_blob *blob) +size_t git_blob_rawsize(git_blob *blob) { assert(blob); return blob->odb_object->raw.len; } +int git_blob__getbuf(git_buf *buffer, git_blob *blob) +{ + return git_buf_set( + buffer, blob->odb_object->raw.data, blob->odb_object->raw.len); +} + void git_blob__free(git_blob *blob) { - git_odb_object_close(blob->odb_object); - free(blob); + git_odb_object_free(blob->odb_object); + git__free(blob); } int git_blob__parse(git_blob *blob, git_odb_object *odb_obj) @@ -53,79 +42,247 @@ int git_blob__parse(git_blob *blob, git_odb_object *odb_obj) assert(blob); git_cached_obj_incref((git_cached_obj *)odb_obj); blob->odb_object = odb_obj; - return GIT_SUCCESS; + return 0; } int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len) { int error; + git_odb *odb; git_odb_stream *stream; - if ((error = git_odb_open_wstream(&stream, repo->db, len, GIT_OBJ_BLOB)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); - - if ((error = stream->write(stream, buffer, len)) < GIT_SUCCESS) { - stream->free(stream); + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (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); + + stream->free(stream); + return error; +} + +static int write_file_stream( + git_oid *oid, git_odb *odb, const char *path, git_off_t file_size) +{ + int fd, error; + char buffer[4096]; + git_odb_stream *stream = NULL; + + if ((error = git_odb_open_wstream( + &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0) + return error; + + if ((fd = git_futils_open_ro(path)) < 0) { + stream->free(stream); + return -1; } - error = stream->finalize_write(oid, stream); + while (!error && file_size > 0) { + ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); + + if (read_len < 0) { + giterr_set( + GITERR_OS, "Failed to create blob. Can't read whole file"); + error = -1; + } + else if (!(error = stream->write(stream, buffer, read_len))) + file_size -= read_len; + } + + p_close(fd); + + if (!error) + error = stream->finalize_write(oid, stream); + stream->free(stream); + return error; +} - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); +static int write_file_filtered( + git_oid *oid, + git_odb *odb, + const char *full_path, + git_vector *filters) +{ + int error; + git_buf source = GIT_BUF_INIT; + git_buf dest = GIT_BUF_INIT; - return GIT_SUCCESS; + 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); + + /* 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); + + git_buf_free(&dest); + return error; +} + +static int write_symlink( + git_oid *oid, git_odb *odb, const char *path, size_t link_size) +{ + char *link_data; + ssize_t read_len; + int error; + + link_data = git__malloc(link_size); + GITERR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, link_size); + if (read_len != (ssize_t)link_size) { + giterr_set(GITERR_OS, "Failed to create blob. Can't read symlink '%s'", path); + git__free(link_data); + return -1; + } + + error = git_odb_write(oid, odb, (void *)link_data, link_size, GIT_OBJ_BLOB); + git__free(link_data); + 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 error; + struct stat st; + git_odb *odb = NULL; + git_off_t size; + + 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; + + size = st.st_size; + + if (S_ISLNK(st.st_mode)) { + error = write_symlink(oid, odb, content_path, (size_t)size); + } else { + git_vector write_filters = GIT_VECTOR_INIT; + int filter_count = 0; + + 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); + } + + if (filter_count < 0) { + /* Negative value means there was a critical error */ + error = filter_count; + } else if (filter_count == 0) { + /* 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 { + /* We need to apply one or more filters */ + error = write_file_filtered(oid, odb, content_path, &write_filters); + } + + git_filters_free(&write_filters); + + /* + * TODO: eventually support streaming filtered files, for files + * which are bigger than a given threshold. This is not a priority + * because applying a filter in streaming mode changes the final + * size of the blob, and without knowing its final size, the blob + * cannot be written in stream mode to the ODB. + * + * The plan is to do streaming writes to a tempfile on disk and then + * opening streaming that file to the ODB, using + * `write_file_stream`. + * + * CAREFULLY DESIGNED APIS YO + */ + } + + return error; } int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path) { - int error, fd; - char full_path[GIT_PATH_MAX]; - char buffer[2048]; - git_off_t size; - git_odb_stream *stream; + git_buf full_path = GIT_BUF_INIT; + const char *workdir; + int error; - if (repo->path_workdir == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to create blob. (No working directory found)"); + workdir = git_repository_workdir(repo); + assert(workdir); /* error to call this on bare repo */ - git__joinpath(full_path, repo->path_workdir, path); - - if ((fd = gitfo_open(full_path, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", full_path); - - if ((size = gitfo_size(fd)) < 0 || !git__is_sizet(size)) { - gitfo_close(fd); - return git__throw(GIT_EOSERR, "Failed to create blob. '%s' appears to be corrupted", full_path); + if (git_buf_joinpath(&full_path, workdir, path) < 0) { + git_buf_free(&full_path); + return -1; } - if ((error = git_odb_open_wstream(&stream, repo->db, (size_t)size, GIT_OBJ_BLOB)) < GIT_SUCCESS) { - gitfo_close(fd); - return git__rethrow(error, "Failed to create blob"); - } + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); - while (size > 0) { - ssize_t read_len; - - read_len = read(fd, buffer, sizeof(buffer)); - - if (read_len < 0) { - gitfo_close(fd); - stream->free(stream); - return git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); - } - - stream->write(stream, buffer, read_len); - size -= read_len; - } - - error = stream->finalize_write(oid, stream); - stream->free(stream); - gitfo_close(fd); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); - - return GIT_SUCCESS; + git_buf_free(&full_path); + return error; } +int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path) +{ + int error; + git_buf full_path = GIT_BUF_INIT; + + if ((error = git_path_prettify(&full_path, path, NULL)) < 0) { + git_buf_free(&full_path); + return error; + } + + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + + git_buf_free(&full_path); + return error; +} + +#define BUFFER_SIZE 4096 + +int git_blob_create_fromchunks( + git_oid *oid, + git_repository *repo, + const char *hintpath, + int (*source_cb)(char *content, size_t max_length, void *payload), + void *payload) +{ + int error = -1, read_bytes; + char *content = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + + content = git__malloc(BUFFER_SIZE); + GITERR_CHECK_ALLOC(content); + + if (git_filebuf_open(&file, hintpath == NULL ? "streamed" : hintpath, GIT_FILEBUF_TEMPORARY) < 0) + goto cleanup; + + while (1) { + read_bytes = source_cb(content, BUFFER_SIZE, payload); + + assert(read_bytes <= BUFFER_SIZE); + + if (read_bytes <= 0) + break; + + if (git_filebuf_write(&file, content, read_bytes) < 0) + goto cleanup; + } + + if (read_bytes < 0) + goto cleanup; + + if (git_filebuf_flush(&file) < 0) + goto cleanup; + + error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); + +cleanup: + git_filebuf_cleanup(&file); + git__free(content); + return error; +} diff --git a/src/blob.h b/src/blob.h index 4300d7e54..0305e9473 100644 --- a/src/blob.h +++ b/src/blob.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_blob_h__ #define INCLUDE_blob_h__ @@ -13,5 +19,6 @@ struct git_blob { void git_blob__free(git_blob *blob); int git_blob__parse(git_blob *blob, git_odb_object *obj); +int git_blob__getbuf(git_buf *buffer, git_blob *blob); #endif diff --git a/src/branch.c b/src/branch.c new file mode 100644 index 000000000..5d5a24038 --- /dev/null +++ b/src/branch.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "commit.h" +#include "branch.h" +#include "tag.h" + +static int retrieve_branch_reference( + git_reference **branch_reference_out, + git_repository *repo, + const char *branch_name, + int is_remote) +{ + git_reference *branch; + int error = -1; + char *prefix; + git_buf ref_name = GIT_BUF_INIT; + + *branch_reference_out = NULL; + + prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; + + if (git_buf_joinpath(&ref_name, prefix, branch_name) < 0) + goto cleanup; + + if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) { + giterr_set(GITERR_REFERENCE, + "Cannot locate %s branch '%s'.", is_remote ? "remote-tracking" : "local", branch_name); + goto cleanup; + } + + *branch_reference_out = branch; + +cleanup: + git_buf_free(&ref_name); + return error; +} + +static int create_error_invalid(const char *msg) +{ + giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg); + return -1; +} + +int git_branch_create( + git_oid *oid_out, + git_repository *repo, + const char *branch_name, + const git_object *target, + int force) +{ + git_otype target_type = GIT_OBJ_BAD; + git_object *commit = NULL; + git_reference *branch = NULL; + git_buf canonical_branch_name = GIT_BUF_INIT; + int error = -1; + + assert(repo && branch_name && target && oid_out); + + if (git_object_owner(target) != repo) + return create_error_invalid("The given target does not belong to this repository"); + + target_type = git_object_type(target); + + switch (target_type) + { + case GIT_OBJ_TAG: + if (git_tag_peel(&commit, (git_tag *)target) < 0) + goto cleanup; + + if (git_object_type(commit) != GIT_OBJ_COMMIT) { + create_error_invalid("The given target does not resolve to a commit"); + goto cleanup; + } + break; + + case GIT_OBJ_COMMIT: + commit = (git_object *)target; + break; + + default: + return create_error_invalid("Only git_tag and git_commit objects are valid targets."); + } + + if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) + goto cleanup; + + if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) + goto cleanup; + + git_oid_cpy(oid_out, git_reference_oid(branch)); + error = 0; + +cleanup: + if (target_type == GIT_OBJ_TAG) + git_object_free(commit); + + git_reference_free(branch); + git_buf_free(&canonical_branch_name); + return error; +} + +int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_t branch_type) +{ + git_reference *branch = NULL; + git_reference *head = NULL; + int error; + + assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE)); + + if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0) + return error; + + if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_REFERENCE, "Cannot locate HEAD."); + goto on_error; + } + + if ((git_reference_type(head) == GIT_REF_SYMBOLIC) + && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) { + giterr_set(GITERR_REFERENCE, + "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name); + goto on_error; + } + + if (git_reference_delete(branch) < 0) + goto on_error; + + git_reference_free(head); + return 0; + +on_error: + git_reference_free(head); + git_reference_free(branch); + return -1; +} + +typedef struct { + git_vector *branchlist; + unsigned int branch_type; +} branch_filter_data; + +static int branch_list_cb(const char *branch_name, void *payload) +{ + branch_filter_data *filter = (branch_filter_data *)payload; + + if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0) + || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)) + return git_vector_insert(filter->branchlist, git__strdup(branch_name)); + + return 0; +} + +int git_branch_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags) +{ + int error; + branch_filter_data filter; + git_vector branchlist; + + assert(branch_names && repo); + + if (git_vector_init(&branchlist, 8, NULL) < 0) + return -1; + + filter.branchlist = &branchlist; + filter.branch_type = list_flags; + + error = git_reference_foreach(repo, GIT_REF_LISTALL, &branch_list_cb, (void *)&filter); + if (error < 0) { + git_vector_free(&branchlist); + return -1; + } + + branch_names->strings = (char **)branchlist.contents; + branch_names->count = branchlist.length; + return 0; +} + +int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force) +{ + git_reference *reference = NULL; + git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; + int error = 0; + + if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) + goto cleanup; + + /* We need to be able to return GIT_ENOTFOUND */ + if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0) + goto cleanup; + + if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) + goto cleanup; + + error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force); + +cleanup: + git_reference_free(reference); + git_buf_free(&old_reference_name); + git_buf_free(&new_reference_name); + + return error; +} diff --git a/src/branch.h b/src/branch.h new file mode 100644 index 000000000..d0e5abc8b --- /dev/null +++ b/src/branch.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_branch_h__ +#define INCLUDE_branch_h__ + +#include "git2/branch.h" + +struct git_branch { + char *remote; /* TODO: Make this a git_remote */ + char *merge; +}; + +#endif diff --git a/src/bswap.h b/src/bswap.h index b9211c3c8..995767a14 100644 --- a/src/bswap.h +++ b/src/bswap.h @@ -1,8 +1,8 @@ /* - * Let's make sure we always have a sane definition for ntohl()/htonl(). - * Some libraries define those as a function call, just to perform byte - * shifting, bringing significant overhead to what should be a simple - * operation. + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -14,8 +14,8 @@ GIT_INLINE(uint32_t) default_swab32(uint32_t val) { return (((val & 0xff000000) >> 24) | - ((val & 0x00ff0000) >> 8) | - ((val & 0x0000ff00) << 8) | + ((val & 0x00ff0000) >> 8) | + ((val & 0x0000ff00) << 8) | ((val & 0x000000ff) << 24)); } diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 000000000..783a36eb8 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "buffer.h" +#include "posix.h" +#include +#include + +/* Used as default value for git_buf->ptr so that people can always + * assume ptr is non-NULL and zero terminated even for new git_bufs. + */ +char git_buf__initbuf[1]; + +char git_buf__oom[1]; + +#define ENSURE_SIZE(b, d) \ + if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\ + return -1; + + +void git_buf_init(git_buf *buf, size_t initial_size) +{ + buf->asize = 0; + buf->size = 0; + buf->ptr = git_buf__initbuf; + + if (initial_size) + git_buf_grow(buf, initial_size); +} + +int git_buf_grow(git_buf *buf, size_t target_size) +{ + int error = git_buf_try_grow(buf, target_size); + if (error != 0) + buf->ptr = git_buf__oom; + return error; +} + +int git_buf_try_grow(git_buf *buf, size_t target_size) +{ + char *new_ptr; + size_t new_size; + + if (buf->ptr == git_buf__oom) + return -1; + + if (target_size <= buf->asize) + return 0; + + if (buf->asize == 0) { + new_size = target_size; + new_ptr = NULL; + } else { + new_size = buf->asize; + new_ptr = buf->ptr; + } + + /* grow the buffer size by 1.5, until it's big enough + * to fit our target size */ + while (new_size < target_size) + new_size = (new_size << 1) - (new_size >> 1); + + /* round allocation up to multiple of 8 */ + new_size = (new_size + 7) & ~7; + + new_ptr = git__realloc(new_ptr, new_size); + if (!new_ptr) + return -1; + + buf->asize = new_size; + buf->ptr = new_ptr; + + /* truncate the existing buffer size if necessary */ + if (buf->size >= buf->asize) + buf->size = buf->asize - 1; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_buf_free(git_buf *buf) +{ + if (!buf) return; + + if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom) + git__free(buf->ptr); + + git_buf_init(buf, 0); +} + +void git_buf_clear(git_buf *buf) +{ + buf->size = 0; + if (buf->asize > 0) + buf->ptr[0] = '\0'; +} + +int git_buf_set(git_buf *buf, const char *data, size_t len) +{ + if (len == 0 || data == NULL) { + git_buf_clear(buf); + } else { + if (data != buf->ptr) { + ENSURE_SIZE(buf, len + 1); + memmove(buf->ptr, data, len); + } + buf->size = len; + buf->ptr[buf->size] = '\0'; + } + return 0; +} + +int git_buf_sets(git_buf *buf, const char *string) +{ + return git_buf_set(buf, string, string ? strlen(string) : 0); +} + +int git_buf_putc(git_buf *buf, char c) +{ + ENSURE_SIZE(buf, buf->size + 2); + buf->ptr[buf->size++] = c; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_buf_put(git_buf *buf, const char *data, size_t len) +{ + ENSURE_SIZE(buf, buf->size + len + 1); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_buf_puts(git_buf *buf, const char *string) +{ + assert(string); + return git_buf_put(buf, string, strlen(string)); +} + +int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) +{ + int len; + + ENSURE_SIZE(buf, buf->size + (strlen(format) * 2)); + + while (1) { + va_list args; + va_copy(args, ap); + + len = p_vsnprintf( + buf->ptr + buf->size, + buf->asize - buf->size, + format, args + ); + + if (len < 0) { + git__free(buf->ptr); + buf->ptr = git_buf__oom; + return -1; + } + + if ((size_t)len + 1 <= buf->asize - buf->size) { + buf->size += len; + break; + } + + ENSURE_SIZE(buf, buf->size + len + 1); + } + + return 0; +} + +int git_buf_printf(git_buf *buf, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = git_buf_vprintf(buf, format, ap); + va_end(ap); + + return r; +} + +void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) +{ + size_t copylen; + + assert(data && datasize && buf); + + data[0] = '\0'; + + if (buf->size == 0 || buf->asize <= 0) + return; + + copylen = buf->size; + if (copylen > datasize - 1) + copylen = datasize - 1; + memmove(data, buf->ptr, copylen); + data[copylen] = '\0'; +} + +void git_buf_consume(git_buf *buf, const char *end) +{ + if (end > buf->ptr && end <= buf->ptr + buf->size) { + size_t consumed = end - buf->ptr; + memmove(buf->ptr, end, buf->size - consumed); + buf->size -= consumed; + buf->ptr[buf->size] = '\0'; + } +} + +void git_buf_truncate(git_buf *buf, size_t len) +{ + if (len < buf->size) { + buf->size = len; + 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); + git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx); +} + +void git_buf_swap(git_buf *buf_a, git_buf *buf_b) +{ + git_buf t = *buf_a; + *buf_a = *buf_b; + *buf_b = t; +} + +char *git_buf_detach(git_buf *buf) +{ + char *data = buf->ptr; + + if (buf->asize == 0 || buf->ptr == git_buf__oom) + return NULL; + + git_buf_init(buf, 0); + + return data; +} + +void git_buf_attach(git_buf *buf, char *ptr, size_t asize) +{ + git_buf_free(buf); + + if (ptr) { + buf->ptr = ptr; + buf->size = strlen(ptr); + if (asize) + buf->asize = (asize < buf->size) ? buf->size + 1 : asize; + else /* pass 0 to fall back on strlen + 1 */ + buf->asize = buf->size + 1; + } else { + git_buf_grow(buf, asize); + } +} + +int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) +{ + va_list ap; + int i; + size_t total_size = 0, original_size = buf->size; + char *out, *original = buf->ptr; + + if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) + ++total_size; /* space for initial separator */ + + /* Make two passes to avoid multiple reallocation */ + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char* segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + segment_len = strlen(segment); + total_size += segment_len; + if (segment_len == 0 || segment[segment_len - 1] != separator) + ++total_size; /* space for separator */ + } + va_end(ap); + + /* expand buffer if needed */ + if (total_size == 0) + return 0; + if (git_buf_grow(buf, buf->size + total_size + 1) < 0) + return -1; + + out = buf->ptr + buf->size; + + /* append separator to existing buf if needed */ + if (buf->size > 0 && out[-1] != separator) + *out++ = separator; + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char* segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + /* deal with join that references buffer's original content */ + if (segment >= original && segment < original + original_size) { + size_t offset = (segment - original); + segment = buf->ptr + offset; + segment_len = original_size - offset; + } else { + segment_len = strlen(segment); + } + + /* skip leading separators */ + if (out > buf->ptr && out[-1] == separator) + while (segment_len > 0 && *segment == separator) { + segment++; + segment_len--; + } + + /* copy over next buffer */ + if (segment_len > 0) { + memmove(out, segment, segment_len); + out += segment_len; + } + + /* append trailing separator (except for last item) */ + if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) + *out++ = separator; + } + va_end(ap); + + /* set size based on num characters actually written */ + buf->size = out - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_buf_join( + git_buf *buf, + char separator, + const char *str_a, + const char *str_b) +{ + size_t strlen_a = str_a ? strlen(str_a) : 0; + size_t strlen_b = strlen(str_b); + int need_sep = 0; + ssize_t offset_a = -1; + + /* not safe to have str_b point internally to the buffer */ + assert(str_b < buf->ptr || str_b > buf->ptr + buf->size); + + /* figure out if we need to insert a separator */ + if (separator && strlen_a) { + while (*str_b == separator) { str_b++; strlen_b--; } + if (str_a[strlen_a - 1] != separator) + need_sep = 1; + } + + /* str_a could be part of the buffer */ + if (str_a >= buf->ptr && str_a < buf->ptr + buf->size) + offset_a = str_a - buf->ptr; + + if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0) + return -1; + + /* fix up internal pointers */ + if (offset_a >= 0) + str_a = buf->ptr + offset_a; + + /* do the actual copying */ + if (offset_a != 0) + memmove(buf->ptr, str_a, strlen_a); + if (need_sep) + buf->ptr[strlen_a] = separator; + memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); + + buf->size = strlen_a + strlen_b + need_sep; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_buf_rtrim(git_buf *buf) +{ + while (buf->size > 0) { + if (!git__isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + buf->ptr[buf->size] = '\0'; +} + +int git_buf_cmp(const git_buf *a, const git_buf *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} + +int git_buf_common_prefix(git_buf *buf, const git_strarray *strings) +{ + size_t i; + const char *str, *pfx; + + git_buf_clear(buf); + + if (!strings || !strings->count) + return 0; + + /* initialize common prefix to first string */ + if (git_buf_sets(buf, strings->strings[0]) < 0) + return -1; + + /* go through the rest of the strings, truncating to shared prefix */ + for (i = 1; i < strings->count; ++i) { + + for (str = strings->strings[i], pfx = buf->ptr; + *str && *str == *pfx; str++, pfx++) + /* scanning */; + + git_buf_truncate(buf, pfx - buf->ptr); + + if (!buf->size) + break; + } + + return 0; +} + +bool git_buf_is_binary(const git_buf *buf) +{ + size_t i; + int printable = 0, nonprintable = 0; + + for (i = 0; i < buf->size; i++) { + unsigned char c = buf->ptr[i]; + if (c > 0x1F && c < 0x7F) + printable++; + else if (c == '\0') + return true; + else if (!git__isspace(c)) + nonprintable++; + } + + return ((printable >> 7) < nonprintable); +} + diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 000000000..50c75f64e --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_buffer_h__ +#define INCLUDE_buffer_h__ + +#include "common.h" +#include + +typedef struct { + char *ptr; + size_t asize, size; +} git_buf; + +extern char git_buf__initbuf[]; +extern char git_buf__oom[]; + +#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 } + +/** + * Initialize a git_buf structure. + * + * For the cases where GIT_BUF_INIT cannot be used to do static + * initialization. + */ +void git_buf_init(git_buf *buf, size_t initial_size); + +/** + * 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. The existing + * contents of the buffer will be preserved however. + * @return 0 on success or -1 on failure + */ +int git_buf_grow(git_buf *buf, size_t target_size); + +/** + * Attempt to grow the buffer to hold at least `target_size` bytes. + * + * This is just like `git_buf_grow` except that even if the allocation + * fails, the git_buf will still be left in a valid state. + */ +int git_buf_try_grow(git_buf *buf, size_t target_size); + +void git_buf_free(git_buf *buf); +void git_buf_swap(git_buf *buf_a, git_buf *buf_b); +char *git_buf_detach(git_buf *buf); +void git_buf_attach(git_buf *buf, char *ptr, size_t asize); + +/** + * Test if there have been any reallocation failures with this git_buf. + * + * Any function that writes to a git_buf can fail due to memory allocation + * issues. If one fails, the git_buf will be marked with an OOM error and + * further calls to modify the buffer will fail. Check git_buf_oom() at the + * end of your sequence and it will be true if you ran out of memory at any + * point with that buffer. + * + * @return false if no error, true if allocation error + */ +GIT_INLINE(bool) git_buf_oom(const git_buf *buf) +{ + return (buf->ptr == git_buf__oom); +} + +/* + * Functions below that return int value error codes will return 0 on + * success or -1 on failure (which generally means an allocation failed). + * Using a git_buf where the allocation has failed with result in -1 from + * all further calls using that buffer. As a result, you can ignore the + * 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); +int git_buf_puts(git_buf *buf, const char *string); +int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); +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_rtruncate_at_char(git_buf *path, char separator); + +int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); +int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b); + +/** + * Join two strings as paths, inserting a slash between as needed. + * @return 0 on success, -1 on failure + */ +GIT_INLINE(int) git_buf_joinpath(git_buf *buf, const char *a, const char *b) +{ + return git_buf_join(buf, '/', a, b); +} + +GIT_INLINE(const char *) git_buf_cstr(const git_buf *buf) +{ + return buf->ptr; +} + +GIT_INLINE(size_t) git_buf_len(const git_buf *buf) +{ + return buf->size; +} + +void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf); + +#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1) + +GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) +{ + ssize_t idx = (ssize_t)buf->size - 1; + while (idx >= 0 && buf->ptr[idx] == ch) idx--; + while (idx >= 0 && buf->ptr[idx] != ch) idx--; + return idx; +} + +/* Remove whitespace from the end of the buffer */ +void git_buf_rtrim(git_buf *buf); + +int git_buf_cmp(const git_buf *a, const git_buf *b); + +/* Fill buf with the common prefix of a array of strings */ +int git_buf_common_prefix(git_buf *buf, const git_strarray *strings); + +/* Check if buffer looks like it contains binary data */ +bool git_buf_is_binary(const git_buf *buf); + +#endif diff --git a/src/cache.c b/src/cache.c index 433fc3d9c..31da3c36e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,63 +1,34 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "repository.h" #include "commit.h" #include "thread-utils.h" +#include "util.h" #include "cache.h" int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { - size_t i; - if (size < 8) size = 8; + size = git__size_t_powerof2(size); - /* round up size to closest power of 2 */ - size--; - size |= size >> 1; - size |= size >> 2; - size |= size >> 4; - size |= size >> 8; - size |= size >> 16; - - cache->size_mask = size; + cache->size_mask = size - 1; cache->lru_count = 0; cache->free_obj = free_ptr; - cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); - if (cache->nodes == NULL) - return GIT_ENOMEM; + git_mutex_init(&cache->lock); - for (i = 0; i < (size + 1); ++i) { - git_mutex_init(&cache->nodes[i].lock); - cache->nodes[i].ptr = NULL; - } + cache->nodes = git__malloc(size * sizeof(git_cached_obj *)); + GITERR_CHECK_ALLOC(cache->nodes); - return GIT_SUCCESS; + memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *)); + return 0; } void git_cache_free(git_cache *cache) @@ -65,65 +36,64 @@ void git_cache_free(git_cache *cache) size_t i; for (i = 0; i < (cache->size_mask + 1); ++i) { - if (cache->nodes[i].ptr) - git_cached_obj_decref(cache->nodes[i].ptr, cache->free_obj); - - git_mutex_free(&cache->nodes[i].lock); + if (cache->nodes[i] != NULL) + git_cached_obj_decref(cache->nodes[i], cache->free_obj); } - free(cache->nodes); + git__free(cache->nodes); } void *git_cache_get(git_cache *cache, const git_oid *oid) { - const uint32_t *hash; - cache_node *node = NULL; - void *result = NULL; + uint32_t hash; + git_cached_obj *node = NULL, *result = NULL; - hash = (const uint32_t *)oid->id; - node = &cache->nodes[hash[0] & cache->size_mask]; + memcpy(&hash, oid->id, sizeof(hash)); - git_mutex_lock(&node->lock); + git_mutex_lock(&cache->lock); { - if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_incref(node->ptr); - result = node->ptr; + node = cache->nodes[hash & cache->size_mask]; + + if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) { + git_cached_obj_incref(node); + result = node; } } - git_mutex_unlock(&node->lock); + git_mutex_unlock(&cache->lock); return result; } -void *git_cache_try_store(git_cache *cache, void *entry) +void *git_cache_try_store(git_cache *cache, void *_entry) { - const uint32_t *hash; - const git_oid *oid; - cache_node *node = NULL; + git_cached_obj *entry = _entry; + uint32_t hash; - oid = &((git_cached_obj*)entry)->oid; - hash = (const uint32_t *)oid->id; - node = &cache->nodes[hash[0] & cache->size_mask]; + memcpy(&hash, &entry->oid, sizeof(hash)); /* increase the refcount on this object, because * the cache now owns it */ git_cached_obj_incref(entry); - git_mutex_lock(&node->lock); - if (node->ptr == NULL) { - node->ptr = entry; - } else if (git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_decref(entry, cache->free_obj); - entry = node->ptr; - } else { - git_cached_obj_decref(node->ptr, cache->free_obj); - node->ptr = entry; + git_mutex_lock(&cache->lock); + { + git_cached_obj *node = cache->nodes[hash & cache->size_mask]; + + if (node == NULL) { + cache->nodes[hash & cache->size_mask] = entry; + } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) { + git_cached_obj_decref(entry, cache->free_obj); + entry = node; + } else { + git_cached_obj_decref(node, cache->free_obj); + cache->nodes[hash & cache->size_mask] = entry; + } } + git_mutex_unlock(&cache->lock); /* increase the refcount again, because we are * returning it to the user */ git_cached_obj_incref(entry); - git_mutex_unlock(&node->lock); return entry; } diff --git a/src/cache.h b/src/cache.h index 4794dea3a..6dc706897 100644 --- a/src/cache.h +++ b/src/cache.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_cache_h__ #define INCLUDE_cache_h__ @@ -17,42 +23,32 @@ typedef struct { } git_cached_obj; typedef struct { - git_cached_obj *ptr; + git_cached_obj **nodes; git_mutex lock; -} cache_node; - -typedef struct { - cache_node *nodes; unsigned int lru_count; size_t size_mask; git_cached_obj_freeptr free_obj; } git_cache; - int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); void git_cache_free(git_cache *cache); void *git_cache_try_store(git_cache *cache, void *entry); void *git_cache_get(git_cache *cache, const git_oid *oid); - -GIT_INLINE(int) git_cached_obj_compare(git_cached_obj *obj, const git_oid *oid) -{ - return git_oid_cmp(&obj->oid, oid); -} - -GIT_INLINE(void) git_cached_obj_incref(git_cached_obj *obj) +GIT_INLINE(void) git_cached_obj_incref(void *_obj) { + git_cached_obj *obj = _obj; git_atomic_inc(&obj->refcount); } -GIT_INLINE(void) git_cached_obj_decref(git_cached_obj *obj, git_cached_obj_freeptr free_obj) +GIT_INLINE(void) git_cached_obj_decref(void *_obj, git_cached_obj_freeptr free_obj) { + git_cached_obj *obj = _obj; + if (git_atomic_dec(&obj->refcount) == 0) free_obj(obj); } - - #endif diff --git a/src/cc-compat.h b/src/cc-compat.h index cf6cccf12..9f23dcae2 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -1,5 +1,8 @@ /* - * cc-compat.h - C compiler compat macros for internal use + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #ifndef INCLUDE_compat_h__ #define INCLUDE_compat_h__ @@ -8,66 +11,59 @@ * See if our compiler is known to support flexible array members. */ #ifndef GIT_FLEX_ARRAY -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_FLEX_ARRAY /* empty */ -# elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define GIT_FLEX_ARRAY /* empty */ -# else -# define GIT_FLEX_ARRAY 0 /* older GNU extension */ -# endif -# endif +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_FLEX_ARRAY /* empty */ +# elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define GIT_FLEX_ARRAY /* empty */ +# else +# define GIT_FLEX_ARRAY 0 /* older GNU extension */ +# endif +# endif /* Default to safer but a bit wasteful traditional style */ -# ifndef GIT_FLEX_ARRAY -# define GIT_FLEX_ARRAY 1 -# endif +# ifndef GIT_FLEX_ARRAY +# define GIT_FLEX_ARRAY 1 +# endif #endif #ifdef __GNUC__ -# define GIT_TYPEOF(x) (__typeof__(x)) +# define GIT_TYPEOF(x) (__typeof__(x)) #else -# define GIT_TYPEOF(x) +# define GIT_TYPEOF(x) #endif -#ifdef __cplusplus -# define GIT_UNUSED(x) -#else -# ifdef __GNUC__ -# define GIT_UNUSED(x) x __attribute__ ((__unused__)) -# else -# define GIT_UNUSED(x) x -# endif -#endif - -#if defined(_MSC_VER) -#define GIT_UNUSED_ARG(x) ((void)(x)); /* note trailing ; */ -#else -#define GIT_UNUSED_ARG(x) -#endif - -/* - * Does our compiler/platform support the C99 and - * header files. (C99 requires that - * includes ). - */ -#if !defined(_MSC_VER) -# define GIT_HAVE_INTTYPES_H 1 -#endif +#define GIT_UNUSED(x) ((void)(x)) /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) -# define PRIuZ "Iu" +# define PRIuZ "Iu" #else -# define PRIuZ "zu" +# define PRIuZ "zu" #endif /* Micosoft Visual C/C++ */ #if defined(_MSC_VER) /* disable "deprecated function" warnings */ -# pragma warning ( disable : 4996 ) +# pragma warning ( disable : 4996 ) /* disable "conditional expression is constant" level 4 warnings */ -# pragma warning ( disable : 4127 ) +# pragma warning ( disable : 4127 ) +#endif + +#if defined (_MSC_VER) + typedef unsigned char bool; +# define true 1 +# define false 0 +#else +# include +#endif + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif #endif #endif /* INCLUDE_compat_h__ */ diff --git a/src/commit.c b/src/commit.c index bfae0592e..2f40dc67d 100644 --- a/src/commit.c +++ b/src/commit.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/common.h" @@ -32,25 +14,17 @@ #include "odb.h" #include "commit.h" #include "signature.h" +#include "message.h" #include -#define COMMIT_BASIC_PARSE 0x0 -#define COMMIT_FULL_PARSE 0x1 - -#define COMMIT_PRINT(commit) {\ - char oid[41]; oid[40] = 0;\ - git_oid_fmt(oid, &commit->object.id);\ - printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\ -} - static void clear_parents(git_commit *commit) { unsigned int i; for (i = 0; i < commit->parent_oids.length; ++i) { git_oid *parent = git_vector_get(&commit->parent_oids, i); - free(parent); + git__free(parent); } git_vector_clear(&commit->parent_oids); @@ -64,9 +38,9 @@ void git_commit__free(git_commit *commit) git_signature_free(commit->author); git_signature_free(commit->committer); - free(commit->message); - free(commit->message_short); - free(commit); + git__free(commit->message); + git__free(commit->message_encoding); + git__free(commit); } const git_oid *git_commit_id(git_commit *c) @@ -74,97 +48,97 @@ const git_oid *git_commit_id(git_commit *c) return git_object_id((git_object *)c); } - int git_commit_create_v( git_oid *oid, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, - const char *message, - const git_oid *tree_oid, - int parent_count, - ...) -{ - va_list ap; - int i, error; - const git_oid **oids; - - oids = git__malloc(parent_count * sizeof(git_oid *)); - - va_start(ap, parent_count); - for (i = 0; i < parent_count; ++i) - oids[i] = va_arg(ap, const git_oid *); - va_end(ap); - - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - tree_oid, parent_count, oids); - - free((void *)oids); - - return error; -} - -int git_commit_create_ov( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, + const char *message_encoding, const char *message, const git_tree *tree, int parent_count, ...) { va_list ap; - int i, error; - const git_oid **oids; + int i, res; + const git_commit **parents; - oids = git__malloc(parent_count * sizeof(git_oid *)); + parents = git__malloc(parent_count * sizeof(git_commit *)); + GITERR_CHECK_ALLOC(parents); va_start(ap, parent_count); for (i = 0; i < parent_count; ++i) - oids[i] = git_object_id(va_arg(ap, const git_object *)); + parents[i] = va_arg(ap, const git_commit *); va_end(ap); - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - git_object_id((git_object *)tree), - parent_count, oids); + res = git_commit_create( + oid, repo, update_ref, author, committer, + message_encoding, message, + tree, parent_count, parents); - free((void *)oids); - - return error; + git__free((void *)parents); + return res; } -int git_commit_create_o( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message, - const git_tree *tree, - int parent_count, - const git_commit *parents[]) +/* Update the reference named `ref_name` so it points to `oid` */ +static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name) { - int i, error; - const git_oid **oids; + git_reference *ref; + int res; - oids = git__malloc(parent_count * sizeof(git_oid *)); + res = git_reference_lookup(&ref, repo, ref_name); - for (i = 0; i < parent_count; ++i) - oids[i] = git_object_id((git_object *)parents[i]); + /* If we haven't found the reference at all, we assume we need to create + * a new reference and that's it */ + if (res == GIT_ENOTFOUND) { + giterr_clear(); + return git_reference_create_oid(NULL, repo, ref_name, oid, 1); + } - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - git_object_id((git_object *)tree), - parent_count, oids); - - free((void *)oids); + if (res < 0) + return -1; - return error; + /* If we have found a reference, but it's symbolic, we need to update + * the direct reference it points to */ + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + git_reference *aux; + const char *sym_target; + + /* The target pointed at by this reference */ + sym_target = git_reference_target(ref); + + /* resolve the reference to the target it points to */ + res = git_reference_resolve(&aux, ref); + + /* + * if the symbolic reference pointed to an inexisting ref, + * this is means we're creating a new branch, for example. + * We need to create a new direct reference with that name + */ + if (res == GIT_ENOTFOUND) { + giterr_clear(); + res = git_reference_create_oid(NULL, repo, sym_target, oid, 1); + git_reference_free(ref); + return res; + } + + /* free the original symbolic reference now; not before because + * we're using the `sym_target` pointer */ + git_reference_free(ref); + + if (res < 0) + return -1; + + /* store the newly found direct reference in its place */ + ref = aux; + } + + /* ref is made to point to `oid`: ref is either the original reference, + * or the target of the symbolic reference we've looked up */ + res = git_reference_set_oid(ref, oid); + git_reference_free(ref); + return res; } int git_commit_create( @@ -173,150 +147,137 @@ int git_commit_create( const char *update_ref, const git_signature *author, const git_signature *committer, + const char *message_encoding, const char *message, - const git_oid *tree_oid, + const git_tree *tree, int parent_count, - const git_oid *parents[]) + const git_commit *parents[]) { - size_t final_size = 0; - int message_length, author_length, committer_length; + git_buf commit = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT; + int i; + git_odb *odb; - char *author_str, *committer_str; + assert(git_object_owner((const git_object *)tree) == repo); - int error, i; - git_odb_stream *stream; + git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree)); - message_length = strlen(message); - author_length = git_signature__write(&author_str, "author", author); - committer_length = git_signature__write(&committer_str, "committer", committer); - - if (author_length < 0 || committer_length < 0) - return git__throw(GIT_EINVALIDARGS, "Cannot create commit. Failed to parse signature"); - - final_size += GIT_OID_LINE_LENGTH("tree"); - final_size += GIT_OID_LINE_LENGTH("parent") * parent_count; - final_size += author_length; - final_size += committer_length; - final_size += 1 + message_length; - - if ((error = git_odb_open_wstream(&stream, repo->db, final_size, GIT_OBJ_COMMIT)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); - - git__write_oid(stream, "tree", tree_oid); - - for (i = 0; i < parent_count; ++i) - git__write_oid(stream, "parent", parents[i]); - - stream->write(stream, author_str, author_length); - free(author_str); - - stream->write(stream, committer_str, committer_length); - free(committer_str); - - - stream->write(stream, "\n", 1); - stream->write(stream, message, message_length); - - error = stream->finalize_write(oid, stream); - stream->free(stream); - - if (error == GIT_SUCCESS && update_ref != NULL) { - git_reference *head; - - error = git_reference_lookup(&head, repo, update_ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); - - error = git_reference_resolve(&head, head); - if (error < GIT_SUCCESS) { - if (error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to create commit"); - /* - * The target of the reference was not found. This can happen - * just after a repository has been initialized (the master - * branch doesn't exist yet, as it doesn't have anything to - * point to) or after an orphan checkout, so if the target - * branch doesn't exist yet, create it and return. - */ - return git_reference_create_oid_f(&head, repo, git_reference_target(head), oid); - } - - error = git_reference_set_oid(head, oid); + for (i = 0; i < parent_count; ++i) { + assert(git_object_owner((const git_object *)parents[i]) == repo); + git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i])); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); + git_signature__writebuf(&commit, "author ", author); + git_signature__writebuf(&commit, "committer ", committer); - return GIT_SUCCESS; + if (message_encoding != NULL) + git_buf_printf(&commit, "encoding %s\n", message_encoding); + + git_buf_putc(&commit, '\n'); + + /* Remove comments by default */ + if (git_message_prettify(&cleaned_message, message, 1) < 0) + goto on_error; + + if (git_buf_puts(&commit, git_buf_cstr(&cleaned_message)) < 0) + goto on_error; + + git_buf_free(&cleaned_message); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; + + if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) + goto on_error; + + git_buf_free(&commit); + + if (update_ref != NULL) + return update_reference(repo, oid, update_ref); + + return 0; + +on_error: + git_buf_free(&commit); + git_buf_free(&cleaned_message); + giterr_set(GITERR_OBJECT, "Failed to create commit."); + return -1; } -int commit_parse_buffer(git_commit *commit, const void *data, size_t len) +int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) { - const char *buffer = (char *)data; - const char *buffer_end = (char *)data + len; + const char *buffer = data; + const char *buffer_end = (const char *)data + len; git_oid parent_oid; - int error; git_vector_init(&commit->parent_oids, 4, NULL); - if ((error = git__parse_oid(&commit->tree_oid, &buffer, buffer_end, "tree ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + if (git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ") < 0) + goto bad_buffer; /* * TODO: commit grafts! */ - while (git__parse_oid(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) { + while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == 0) { git_oid *new_oid; new_oid = git__malloc(sizeof(git_oid)); + GITERR_CHECK_ALLOC(new_oid); + git_oid_cpy(new_oid, &parent_oid); - if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&commit->parent_oids, new_oid) < 0) + return -1; } commit->author = git__malloc(sizeof(git_signature)); - if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + GITERR_CHECK_ALLOC(commit->author); + + if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0) + return -1; /* Always parse the committer; we need the commit time */ commit->committer = git__malloc(sizeof(git_signature)); - if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + GITERR_CHECK_ALLOC(commit->committer); - /* parse commit message */ - while (buffer <= buffer_end && *buffer == '\n') - buffer++; + if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) + return -1; - if (buffer < buffer_end) { - const char *line_end; - size_t message_len; + if (git__prefixcmp(buffer, "encoding ") == 0) { + const char *encoding_end; + buffer += strlen("encoding "); - /* Long message */ - message_len = buffer_end - buffer; - commit->message = git__malloc(message_len + 1); - memcpy(commit->message, buffer, message_len); - commit->message[message_len] = 0; + encoding_end = buffer; + while (encoding_end < buffer_end && *encoding_end != '\n') + encoding_end++; - /* Short message */ - if((line_end = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - line_end = buffer_end; - message_len = line_end - buffer; + commit->message_encoding = git__strndup(buffer, encoding_end - buffer); + GITERR_CHECK_ALLOC(commit->message_encoding); - commit->message_short = git__malloc(message_len + 1); - memcpy(commit->message_short, buffer, message_len); - commit->message_short[message_len] = 0; + buffer = encoding_end; } - return GIT_SUCCESS; + /* parse commit message */ + while (buffer < buffer_end - 1 && *buffer == '\n') + buffer++; + + if (buffer <= buffer_end) { + commit->message = git__strndup(buffer, buffer_end - buffer); + GITERR_CHECK_ALLOC(commit->message); + } + + return 0; + +bad_buffer: + giterr_set(GITERR_OBJECT, "Failed to parse bad commit object"); + return -1; } int git_commit__parse(git_commit *commit, git_odb_object *obj) { assert(commit); - return commit_parse_buffer(commit, obj->raw.data, obj->raw.len); + return git_commit__parse_buffer(commit, obj->raw.data, obj->raw.len); } #define GIT_COMMIT_GETTER(_rvalue, _name, _return) \ @@ -329,7 +290,7 @@ int git_commit__parse(git_commit *commit, git_odb_object *obj) 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_short, commit->message_short) +GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) 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, commit->parent_oids.length) @@ -348,8 +309,10 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) assert(commit); parent_oid = git_vector_get(&commit->parent_oids, n); - if (parent_oid == NULL) - return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n); + if (parent_oid == NULL) { + giterr_set(GITERR_INVALID, "Parent %u does not exist", n); + return GIT_ENOTFOUND; + } return git_commit_lookup(parent, commit->object.repo, parent_oid); } diff --git a/src/commit.h b/src/commit.h index 3d15c5044..d9f492862 100644 --- a/src/commit.h +++ b/src/commit.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_commit_h__ #define INCLUDE_commit_h__ @@ -17,11 +23,12 @@ struct git_commit { git_signature *author; git_signature *committer; + char *message_encoding; char *message; - char *message_short; }; void git_commit__free(git_commit *c); int git_commit__parse(git_commit *commit, git_odb_object *obj); +int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len); #endif diff --git a/src/common.h b/src/common.h index f4f11fd2f..e2a300291 100644 --- a/src/common.h +++ b/src/common.h @@ -1,19 +1,15 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_common_h__ #define INCLUDE_common_h__ -/** Force 64 bit off_t size on POSIX. */ -#define _FILE_OFFSET_BITS 64 - -#if defined(_WIN32) && !defined(__CYGWIN__) -#define GIT_WIN32 1 -#endif - -#include "git2/thread-utils.h" +#include "git2/common.h" #include "cc-compat.h" -#ifdef GIT_HAVE_INTTYPES_H -# include -#endif #include #include #include @@ -29,35 +25,47 @@ # include # include # include -# include "msvc-compat.h" -# include "mingw-compat.h" +# include "win32/msvc-compat.h" +# include "win32/mingw-compat.h" # ifdef GIT_THREADS -# include "win32/pthread.h" +# include "win32/pthread.h" #endif # define snprintf _snprintf -typedef SSIZE_T ssize_t; - #else # include -# include # ifdef GIT_THREADS -# include +# include # endif #endif -#include "git2/common.h" #include "git2/types.h" #include "git2/errors.h" #include "thread-utils.h" #include "bswap.h" -#define GIT_PATH_MAX 4096 -extern int git__throw(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); -extern int git__rethrow(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +#include + +extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2); +#define git__throw(error, ...) \ + (git___throw(__VA_ARGS__), error) + +extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2); +#define git__rethrow(error, ...) \ + (git___rethrow(__VA_ARGS__), error) + + +#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } + +void giterr_set_oom(void); +void giterr_set(int error_class, const char *string, ...); +void giterr_clear(void); +void giterr_set_str(int error_class, const char *string); +void giterr_set_regex(const regex_t *regex, int error_code); #include "util.h" + #endif /* INCLUDE_common_h__ */ diff --git a/src/compat/fnmatch.c b/src/compat/fnmatch.c new file mode 100644 index 000000000..835d811bc --- /dev/null +++ b/src/compat/fnmatch.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include +#include +#include + +#include "fnmatch.h" + +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int rangematch(const char *, char, int, char **); + +int +p_fnmatch(const char *pattern, const char *string, int flags) +{ + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || + strchr(string, '/') == NULL ? + 0 : FNM_NOMATCH); + else + return (0); + } else if (c == '/' && (flags & FNM_PATHNAME)) { + if ((string = strchr(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && (flags & FNM_PATHNAME)) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + /* not a good range, treat as normal text */ + goto normal; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + normal: + if (c != *string && !((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string)))) + return (FNM_NOMATCH); + ++string; + break; + } + /* NOTREACHED */ +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^')) != 0) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = (char)tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + if ((flags & FNM_CASEFOLD)) + c = (char)tolower((unsigned char)c); + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + if (flags & FNM_CASEFOLD) + c2 = (char)tolower((unsigned char)c2); + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char *)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} + diff --git a/src/compat/fnmatch.h b/src/compat/fnmatch.h new file mode 100644 index 000000000..7faef09b3 --- /dev/null +++ b/src/compat/fnmatch.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_fnmatch__compat_h__ +#define INCLUDE_fnmatch__compat_h__ + +#include "common.h" + +#define FNM_NOMATCH 1 /* Match failed. */ +#define FNM_NOSYS 2 /* Function not supported (unused). */ + +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore / after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ + +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +extern int p_fnmatch(const char *pattern, const char *string, int flags); + +#endif /* _FNMATCH_H */ + diff --git a/src/config.c b/src/config.c index 29b9b799f..d18b85c30 100644 --- a/src/config.c +++ b/src/config.c @@ -1,34 +1,18 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "fileops.h" -#include "hashtable.h" #include "config.h" #include "git2/config.h" #include "vector.h" +#if GIT_WIN32 +# include +#endif #include @@ -37,55 +21,7 @@ typedef struct { int priority; } file_internal; -int git_config_open_file(git_config **out, const char *path) -{ - git_config_file *file = NULL; - git_config *cfg = NULL; - int error = GIT_SUCCESS; - - error = git_config_new(&cfg); - if (error < GIT_SUCCESS) - return error; - - error = git_config_file__ondisk(&file, path); - if (error < GIT_SUCCESS) { - git_config_free(cfg); - return error; - } - - error = git_config_add_file(cfg, file, 1); - if (error < GIT_SUCCESS) { - file->free(file); - git_config_free(cfg); - return error; - } - - error = file->open(file); - if (error < GIT_SUCCESS) { - git_config_free(cfg); - return git__rethrow(error, "Failed to open config file"); - } - - *out = cfg; - - return GIT_SUCCESS; -} - -int git_config_open_global(git_config **out) -{ - char full_path[GIT_PATH_MAX]; - const char *home; - - home = getenv("HOME"); - if (home == NULL) - return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot find $HOME variable"); - - git__joinpath(full_path, home, GIT_CONFIG_FILENAME); - - return git_config_open_file(out, full_path); -} - -void git_config_free(git_config *cfg) +static void config_free(git_config *cfg) { unsigned int i; git_config_file *file; @@ -95,17 +31,25 @@ void git_config_free(git_config *cfg) internal = git_vector_get(&cfg->files, i); file = internal->file; file->free(file); - free(internal); + git__free(internal); } git_vector_free(&cfg->files); - free(cfg); + git__free(cfg); +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + GIT_REFCOUNT_DEC(cfg, config_free); } static int config_backend_cmp(const void *a, const void *b) { - const file_internal *bk_a = *(const file_internal **)(a); - const file_internal *bk_b = *(const file_internal **)(b); + const file_internal *bk_a = (const file_internal *)(a); + const file_internal *bk_b = (const file_internal *)(b); return bk_b->priority - bk_a->priority; } @@ -115,52 +59,86 @@ int git_config_new(git_config **out) git_config *cfg; cfg = git__malloc(sizeof(git_config)); - if (cfg == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(cfg); memset(cfg, 0x0, sizeof(git_config)); if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) { - free(cfg); - return GIT_ENOMEM; + git__free(cfg); + return -1; } *out = cfg; + GIT_REFCOUNT_INC(cfg); + return 0; +} - return GIT_SUCCESS; +int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority) +{ + git_config_file *file = NULL; + + if (git_config_file__ondisk(&file, path) < 0) + return -1; + + if (git_config_add_file(cfg, file, priority) < 0) { + /* + * free manually; the file is not owned by the config + * instance yet and will not be freed on cleanup + */ + file->free(file); + return -1; + } + + return 0; +} + +int git_config_open_ondisk(git_config **cfg, const char *path) +{ + if (git_config_new(cfg) < 0) + return -1; + + if (git_config_add_file_ondisk(*cfg, path, 1) < 0) { + git_config_free(*cfg); + return -1; + } + + return 0; } int git_config_add_file(git_config *cfg, git_config_file *file, int priority) { file_internal *internal; + int result; assert(cfg && file); + if ((result = file->open(file)) < 0) + return result; + internal = git__malloc(sizeof(file_internal)); - if (internal == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(internal); internal->file = file; internal->priority = priority; if (git_vector_insert(&cfg->files, internal) < 0) { - free(internal); - return GIT_ENOMEM; + git__free(internal); + return -1; } git_vector_sort(&cfg->files); internal->file->cfg = cfg; - return GIT_SUCCESS; + return 0; } /* * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data) +int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) { - int ret = GIT_SUCCESS; + int ret = 0; unsigned int i; file_internal *internal; git_config_file *file; @@ -174,25 +152,33 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d return ret; } +int git_config_delete(git_config *cfg, const char *name) +{ + file_internal *internal; + git_config_file *file; + + assert(cfg->files.length); + + internal = git_vector_get(&cfg->files, 0); + file = internal->file; + + return file->del(file, name); +} /************** * Setters **************/ -/* - * Internal function to actually set the string value of a variable - */ - -int git_config_set_long(git_config *cfg, const char *name, long int value) +int git_config_set_int64(git_config *cfg, const char *name, int64_t value) { char str_value[32]; /* All numbers should fit in here */ - snprintf(str_value, sizeof(str_value), "%ld", value); + p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); return git_config_set_string(cfg, name, str_value); } -int git_config_set_int(git_config *cfg, const char *name, int value) +int git_config_set_int32(git_config *cfg, const char *name, int32_t value) { - return git_config_set_long(cfg, name, value); + return git_config_set_int64(cfg, name, (int64_t)value); } int git_config_set_bool(git_config *cfg, const char *name, int value) @@ -205,8 +191,7 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) file_internal *internal; git_config_file *file; - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot set variable value; no files open in the `git_config` instance"); + assert(cfg->files.length); internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -214,109 +199,309 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return file->set(file, name, value); } -/*********** - * Getters - ***********/ - -int git_config_get_long(git_config *cfg, const char *name, long int *out) +static int parse_int64(int64_t *out, const char *value) { - const char *value, *num_end; - int ret; - long int num; + const char *num_end; + int64_t num; - ret = git_config_get_string(cfg, name, &value); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to get value for %s", name); - - ret = git__strtol32(&num, value, &num_end, 0); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to get value for %s", name); + if (git__strtol64(&num, value, &num_end, 0) < 0) + return -1; switch (*num_end) { - case '\0': - break; + case 'g': + case 'G': + num *= 1024; + /* fallthrough */ + + case 'm': + case 'M': + num *= 1024; + /* fallthrough */ + case 'k': case 'K': num *= 1024; - break; - case 'm': - case 'M': - num *= 1024 * 1024; - break; - case 'g': - case 'G': - num *= 1024 * 1024 * 1024; - break; + + /* check that that there are no more characters after the + * given modifier suffix */ + if (num_end[1] != '\0') + return -1; + + /* fallthrough */ + + case '\0': + *out = num; + return 0; + default: - return git__throw(GIT_EINVALIDTYPE, "Failed to get value for %s. Value is of invalid type", name); + return -1; + } +} + +static int parse_int32(int32_t *out, const char *value) +{ + int64_t tmp; + int32_t truncate; + + if (parse_int64(&tmp, value) < 0) + return -1; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + return -1; + + *out = truncate; + return 0; +} + +/*********** + * Getters + ***********/ +int git_config_lookup_map_value( + git_cvar_map *maps, size_t map_n, const char *value, int *out) +{ + size_t i; + + if (!value) + return GIT_ENOTFOUND; + + for (i = 0; i < map_n; ++i) { + git_cvar_map *m = maps + i; + + switch (m->cvar_type) { + case GIT_CVAR_FALSE: + case GIT_CVAR_TRUE: { + int bool_val; + + if (git__parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->cvar_type) { + *out = m->map_value; + return 0; + } + break; + } + + case GIT_CVAR_INT32: + if (parse_int32(out, value) == 0) + return 0; + break; + + case GIT_CVAR_STRING: + if (strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + break; + } } - *out = num; - - return GIT_SUCCESS; + return GIT_ENOTFOUND; } -int git_config_get_int(git_config *cfg, const char *name, int *out) -{ - long int tmp; - int ret; - - ret = git_config_get_long(cfg, name, &tmp); - - *out = (int) tmp; - - return ret; -} - -int git_config_get_bool(git_config *cfg, const char *name, int *out) +int git_config_get_mapped( + int *out, + git_config *cfg, + const char *name, + git_cvar_map *maps, + size_t map_n) { const char *value; - int error = GIT_SUCCESS; + int ret; - error = git_config_get_string(cfg, name, &value); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; - /* A missing value means true */ - if (value == NULL) { - *out = 1; - return GIT_SUCCESS; - } + if (!git_config_lookup_map_value(maps, map_n, value, out)) + return 0; - if (!strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return GIT_SUCCESS; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off")) { - *out = 0; - return GIT_SUCCESS; - } - - /* Try to parse it as an integer */ - error = git_config_get_int(cfg, name, out); - if (error == GIT_SUCCESS) - *out = !!(*out); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); - return error; + giterr_set(GITERR_CONFIG, + "Failed to map the '%s' config variable with a valid value", name); + return -1; } -int git_config_get_string(git_config *cfg, const char *name, const char **out) +int git_config_get_int64(int64_t *out, git_config *cfg, const char *name) +{ + const char *value; + int ret; + + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; + + if (parse_int64(out, value) < 0) { + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value); + return -1; + } + + return 0; +} + +int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) +{ + const char *value; + int ret; + + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; + + if (parse_int32(out, value) < 0) { + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value); + return -1; + } + + return 0; +} + +int git_config_get_bool(int *out, git_config *cfg, const char *name) +{ + const char *value; + int ret; + + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; + + if (git__parse_bool(out, value) == 0) + return 0; + + if (parse_int32(out, value) == 0) { + *out = !!(*out); + return 0; + } + + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); + return -1; +} + +int git_config_get_string(const char **out, git_config *cfg, const char *name) +{ + file_internal *internal; + unsigned int i; + + assert(cfg->files.length); + + *out = NULL; + + git_vector_foreach(&cfg->files, i, internal) { + git_config_file *file = internal->file; + int ret = file->get(file, name, out); + if (ret != GIT_ENOTFOUND) + return ret; + } + + return GIT_ENOTFOUND; +} + +int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, + int (*fn)(const char *value, void *data), void *data) { file_internal *internal; git_config_file *file; + int ret = GIT_ENOTFOUND; + unsigned int i; - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot get variable value; no files open in the `git_config` instance"); + assert(cfg->files.length); - internal = git_vector_get(&cfg->files, 0); - file = internal->file; + /* + * 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); + file = internal->file; + ret = file->get_multivar(file, name, regexp, fn, data); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + } - return file->get(file, name, out); + return 0; +} + +int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +{ + file_internal *internal; + git_config_file *file; + int ret = GIT_ENOTFOUND; + unsigned int i; + + for (i = cfg->files.length; i > 0; --i) { + internal = git_vector_get(&cfg->files, i - 1); + file = internal->file; + ret = file->set_multivar(file, name, regexp, value); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + } + + return 0; +} + +int git_config_find_global_r(git_buf *path) +{ + return git_futils_find_global_file(path, GIT_CONFIG_FILENAME); +} + +int git_config_find_global(char *global_config_path, size_t length) +{ + git_buf path = GIT_BUF_INIT; + int ret = git_config_find_global_r(&path); + + if (ret < 0) { + git_buf_free(&path); + return ret; + } + + if (path.size >= length) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } + + git_buf_copy_cstr(global_config_path, length, &path); + git_buf_free(&path); + return 0; +} + +int git_config_find_system_r(git_buf *path) +{ + return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config_find_system(char *system_config_path, size_t length) +{ + git_buf path = GIT_BUF_INIT; + int ret = git_config_find_system_r(&path); + + if (ret < 0) { + git_buf_free(&path); + return ret; + } + + if (path.size >= length) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } + + git_buf_copy_cstr(system_config_path, length, &path); + git_buf_free(&path); + return 0; +} + +int git_config_open_global(git_config **out) +{ + int error; + git_buf path = GIT_BUF_INIT; + + if ((error = git_config_find_global_r(&path)) < 0) + return error; + + error = git_config_open_ondisk(out, git_buf_cstr(&path)); + git_buf_free(&path); + + return error; } diff --git a/src/config.h b/src/config.h index 8b521543c..82e98ce51 100644 --- a/src/config.h +++ b/src/config.h @@ -1,14 +1,33 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ #ifndef INCLUDE_config_h__ #define INCLUDE_config_h__ #include "git2.h" #include "git2/config.h" #include "vector.h" +#include "repository.h" #define GIT_CONFIG_FILENAME ".gitconfig" +#define GIT_CONFIG_FILENAME_INREPO "config" +#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" +#define GIT_CONFIG_FILE_MODE 0666 struct git_config { + git_refcount rc; git_vector files; }; +extern int git_config_find_global_r(git_buf *global_config_path); +extern int git_config_find_system_r(git_buf *system_config_path); + +extern int git_config_parse_bool(int *out, const char *bool_string); + +extern int git_config_lookup_map_value( + git_cvar_map *maps, size_t map_n, const char *value, int *out); + #endif diff --git a/src/config_cache.c b/src/config_cache.c new file mode 100644 index 000000000..ca9602e56 --- /dev/null +++ b/src/config_cache.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "config.h" +#include "git2/config.h" +#include "vector.h" +#include "filter.h" +#include "repository.h" + +struct map_data { + const char *cvar_name; + git_cvar_map *maps; + size_t map_count; + int default_value; +}; + +/* + * core.eol + * Sets the line ending type to use in the working directory for + * files that have the text property set. Alternatives are lf, crlf + * and native, which uses the platform’s native line ending. The default + * value is native. See gitattributes(5) for more information on + * end-of-line conversion. + */ +static git_cvar_map _cvar_map_eol[] = { + {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET}, + {GIT_CVAR_STRING, "lf", GIT_EOL_LF}, + {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF}, + {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE} +}; + +/* + * core.autocrlf + * Setting this variable to "true" is almost the same as setting + * the text attribute to "auto" on all files except that text files are + * not guaranteed to be normalized: files that contain CRLF in the + * repository will not be touched. Use this setting if you want to have + * CRLF line endings in your working directory even though the repository + * does not have normalized line endings. This variable can be set to input, + * in which case no output conversion is performed. + */ +static git_cvar_map _cvar_map_autocrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} +}; + +static struct map_data _cvar_maps[] = { + {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT}, + {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT} +}; + +int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) +{ + *out = repo->cvar_cache[(int)cvar]; + + if (*out == GIT_CVAR_NOT_CACHED) { + struct map_data *data = &_cvar_maps[(int)cvar]; + git_config *config; + int error; + + error = git_repository_config__weakptr(&config, repo); + if (error < 0) + return error; + + error = git_config_get_mapped(out, + config, data->cvar_name, data->maps, data->map_count); + + if (error == GIT_ENOTFOUND) + *out = data->default_value; + + else if (error < 0) + return error; + + repo->cvar_cache[(int)cvar] = *out; + } + + return 0; +} + +void git_repository__cvar_cache_clear(git_repository *repo) +{ + int i; + + for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i) + repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED; +} + diff --git a/src/config_file.c b/src/config_file.c index d76c6024d..1c748fad1 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1,40 +1,28 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "config.h" #include "fileops.h" +#include "filebuf.h" +#include "buffer.h" #include "git2/config.h" #include "git2/types.h" +#include "strmap.h" #include +#include +#include + +GIT__USE_STRMAP; typedef struct cvar_t { struct cvar_t *next; - char *section; - char *name; + char *key; /* TODO: we might be able to get rid of this */ char *value; } cvar_t; @@ -84,10 +72,10 @@ typedef struct { typedef struct { git_config_file parent; - cvar_t_list var_list; + git_strmap *values; struct { - gitfo_buf buffer; + git_buf buffer; char *read_ptr; int line_number; int eof; @@ -98,171 +86,93 @@ typedef struct { static int config_parse(diskfile_backend *cfg_file); static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value); +static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); + +static void set_parse_error(diskfile_backend *backend, 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); +} static void cvar_free(cvar_t *var) { if (var == NULL) return; - free(var->section); - free(var->name); - free(var->value); - free(var); + git__free(var->key); + git__free(var->value); + git__free(var); } -static void cvar_list_free(cvar_t_list *list) +/* Take something the user gave us and make it nice for our hash function */ +static int normalize_name(const char *in, char **out) { - cvar_t *cur; + char *name, *fdot, *ldot; - while (!CVAR_LIST_EMPTY(list)) { - cur = CVAR_LIST_HEAD(list); - CVAR_LIST_REMOVE_HEAD(list); - cvar_free(cur); - } -} + assert(in && out); -/* - * Compare two strings according to the git section-subsection - * rules. The order of the strings is important because local is - * assumed to have the internal format (only the section name and with - * case information) and input the normalized one (only dots, no case - * information). - */ -static int cvar_match_section(const char *local, const char *input) -{ - char *first_dot, *last_dot; - char *local_sp = strchr(local, ' '); - int comparison_len; + name = git__strdup(in); + GITERR_CHECK_ALLOC(name); - /* - * If the local section name doesn't contain a space, then we can - * just do a case-insensitive compare. - */ - if (local_sp == NULL) - return !strncasecmp(local, input, strlen(local)); + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); - /* - * From here onwards, there is a space diving the section and the - * subsection. Anything before the space in local is - * case-insensitive. - */ - if (strncasecmp(local, input, local_sp - local)) - return 0; - - /* - * We compare starting from the first character after the - * quotation marks, which is two characters beyond the space. For - * the input, we start one character beyond the dot. If the names - * have different lengths, then we can fail early, as we know they - * can't be the same. - * The length is given by the length between the quotation marks. - */ - - first_dot = strchr(input, '.'); - last_dot = strrchr(input, '.'); - comparison_len = strlen(local_sp + 2) - 1; - - if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) - return 0; - - return !strncmp(local_sp + 2, first_dot + 1, comparison_len); -} - -static int cvar_match_name(const cvar_t *var, const char *str) -{ - const char *name_start; - - if (!cvar_match_section(var->section, str)) { - return 0; - } - /* Early exit if the lengths are different */ - name_start = strrchr(str, '.') + 1; - if (strlen(var->name) != strlen(name_start)) - return 0; - - return !strcasecmp(var->name, name_start); -} - -static cvar_t *cvar_list_find(cvar_t_list *list, const char *name) -{ - cvar_t *iter; - - CVAR_LIST_FOREACH (list, iter) { - if (cvar_match_name(iter, name)) - return iter; + if (fdot == NULL || ldot == NULL) { + git__free(name); + giterr_set(GITERR_CONFIG, + "Invalid variable name: '%s'", in); + return -1; } - return NULL; + /* Downcase up to the first dot and after the last one */ + git__strntolower(name, fdot - name); + git__strtolower(ldot); + + *out = name; + return 0; } -static int cvar_normalize_name(cvar_t *var, char **output) +static void free_vars(git_strmap *values) { - char *section_sp = strchr(var->section, ' '); - char *quote, *name; - int len, ret; + cvar_t *var = NULL; - /* - * The final string is going to be at most one char longer than - * the input - */ - len = strlen(var->section) + strlen(var->name) + 1; - name = git__malloc(len + 1); - if (name == NULL) - return GIT_ENOMEM; + if (values == NULL) + return; - /* If there aren't any spaces in the section, it's easy */ - if (section_sp == NULL) { - ret = snprintf(name, len + 1, "%s.%s", var->section, var->name); - if (ret < 0) - return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno)); + git_strmap_foreach_value(values, var, + while (var != NULL) { + cvar_t *next = CVAR_LIST_NEXT(var); + cvar_free(var); + var = next; + }); - *output = name; - return GIT_SUCCESS; - } - - /* - * If there are spaces, we replace the space by a dot, move - * section name so it overwrites the first quotation mark and - * replace the last quotation mark by a dot. We then append the - * variable name. - */ - strcpy(name, var->section); - section_sp = strchr(name, ' '); - *section_sp = '.'; - /* Remove first quote */ - quote = strchr(name, '"'); - memmove(quote, quote+1, strlen(quote+1)); - /* Remove second quote */ - quote = strchr(name, '"'); - *quote = '.'; - strcpy(quote+1, var->name); - - *output = name; - return GIT_SUCCESS; + git_strmap_free(values); } static int config_open(git_config_file *cfg) { - int error; + int res; diskfile_backend *b = (diskfile_backend *)cfg; - error = gitfo_read_file(&b->reader.buffer, b->file_path); - if(error < GIT_SUCCESS) - goto cleanup; + b->values = git_strmap_alloc(); + GITERR_CHECK_ALLOC(b->values); - error = config_parse(b); - if (error < GIT_SUCCESS) - goto cleanup; + git_buf_init(&b->reader.buffer, 0); + res = git_futils_readbuffer(&b->reader.buffer, b->file_path); - gitfo_free_buf(&b->reader.buffer); + /* It's fine if the file doesn't exist */ + if (res == GIT_ENOTFOUND) + return 0; - return error; + if (res < 0 || config_parse(b) < 0) { + free_vars(b->values); + b->values = NULL; + git_buf_free(&b->reader.buffer); + return -1; + } - cleanup: - cvar_list_free(&b->var_list); - gitfo_free_buf(&b->reader.buffer); - - return git__rethrow(error, "Failed to open config"); + git_buf_free(&b->reader.buffer); + return 0; } static void backend_free(git_config_file *_backend) @@ -272,97 +182,94 @@ static void backend_free(git_config_file *_backend) if (backend == NULL) return; - free(backend->file_path); - cvar_list_free(&backend->var_list); - - free(backend); + git__free(backend->file_path); + free_vars(backend->values); + git__free(backend); } -static int file_foreach(git_config_file *backend, int (*fn)(const char *, void *), void *data) +static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) { - int ret = GIT_SUCCESS; - cvar_t *var; diskfile_backend *b = (diskfile_backend *)backend; + cvar_t *var; + const char *key; - CVAR_LIST_FOREACH(&b->var_list, var) { - char *normalized = NULL; + if (!b->values) + return 0; - ret = cvar_normalize_name(var, &normalized); - if (ret < GIT_SUCCESS) - return ret; + git_strmap_foreach(b->values, key, var, + do { + if (fn(key, var->value, data) < 0) + break; - ret = fn(normalized, data); - free(normalized); - if (ret) - break; - } + var = CVAR_LIST_NEXT(var); + } while (var != NULL); + ); - return ret; + return 0; } static int config_set(git_config_file *cfg, const char *name, const char *value) { - cvar_t *var = NULL; - cvar_t *existing = NULL; - int error = GIT_SUCCESS; - const char *last_dot; + cvar_t *var = NULL, *old_var; diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; + int rval; + + if (normalize_name(name, &key) < 0) + return -1; /* - * If it already exists, we just need to update its value. + * Try to find it in the existing values and update it if it + * only has one value. */ - existing = cvar_list_find(&b->var_list, name); - if (existing != NULL) { - char *tmp = value ? git__strdup(value) : NULL; - if (tmp == NULL && value != NULL) - return GIT_ENOMEM; + pos = git_strmap_lookup_index(b->values, key); + if (git_strmap_valid_index(b->values, pos)) { + cvar_t *existing = git_strmap_value_at(b->values, pos); + char *tmp = NULL; - free(existing->value); + git__free(key); + if (existing->next != NULL) { + giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); + return -1; + } + + if (value) { + tmp = git__strdup(value); + GITERR_CHECK_ALLOC(tmp); + } + + git__free(existing->value); existing->value = tmp; - return GIT_SUCCESS; - } - - /* - * Otherwise, create it and stick it at the end of the queue. - */ - - last_dot = strrchr(name, '.'); - if (last_dot == NULL) { - return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed"); + return config_write(b, existing->key, NULL, value); } var = git__malloc(sizeof(cvar_t)); - if (var == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(var); memset(var, 0x0, sizeof(cvar_t)); - var->section = git__strndup(name, last_dot - name); - if (var->section == NULL) { - error = GIT_ENOMEM; - goto out; + var->key = key; + var->value = NULL; + + if (value) { + var->value = git__strdup(value); + GITERR_CHECK_ALLOC(var->value); } - var->name = git__strdup(last_dot + 1); - if (var->name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - var->value = value ? git__strdup(value) : NULL; - if (var->value == NULL && value != NULL) { - error = GIT_ENOMEM; - goto out; - } - - CVAR_LIST_APPEND(&b->var_list, var); - - out: - if (error < GIT_SUCCESS) + if (config_write(b, key, NULL, value) < 0) { cvar_free(var); + return -1; + } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value"); + git_strmap_insert2(b->values, key, var, old_var, rval); + if (rval < 0) + return -1; + if (old_var != NULL) + cvar_free(old_var); + + return 0; } /* @@ -370,18 +277,190 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) */ static int config_get(git_config_file *cfg, const char *name, const char **out) { - cvar_t *var; - int error = GIT_SUCCESS; diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; - var = cvar_list_find(&b->var_list, name); + if (normalize_name(name, &key) < 0) + return -1; - if (var == NULL) - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + pos = git_strmap_lookup_index(b->values, key); + git__free(key); - *out = var->value; + /* no error message; the config system will write one */ + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name); + *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value; + + return 0; +} + +static int config_get_multivar( + git_config_file *cfg, + const char *name, + const char *regex_str, + int (*fn)(const char *, void *), + void *data) +{ + cvar_t *var; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; + + if (normalize_name(name, &key) < 0) + return -1; + + 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); + + 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); + return -1; + } + + /* and throw the callback only on the variables that + * match the regex */ + do { + if (regexec(®ex, var->value, 0, NULL, 0) == 0) { + /* early termination by the user is not an error; + * just break and return successfully */ + if (fn(var->value, 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->value, data) < 0) + break; + + var = var->next; + } while (var != NULL); + } + + return 0; +} + +static int config_set_multivar( + git_config_file *cfg, const char *name, const char *regexp, const char *value) +{ + int replaced = 0; + cvar_t *var, *newvar; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + regex_t preg; + int result; + khiter_t pos; + + assert(regexp); + + if (normalize_name(name, &key) < 0) + return -1; + + pos = git_strmap_lookup_index(b->values, key); + if (!git_strmap_valid_index(b->values, pos)) { + 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); + return -1; + } + + for (;;) { + if (regexec(&preg, var->value, 0, NULL, 0) == 0) { + char *tmp = git__strdup(value); + GITERR_CHECK_ALLOC(tmp); + + git__free(var->value); + var->value = tmp; + replaced = 1; + } + + if (var->next == NULL) + break; + + var = var->next; + } + + /* If we've reached the end of the variables and we haven't found it yet, we need to append it */ + if (!replaced) { + newvar = git__malloc(sizeof(cvar_t)); + GITERR_CHECK_ALLOC(newvar); + + memset(newvar, 0x0, sizeof(cvar_t)); + + newvar->key = git__strdup(var->key); + GITERR_CHECK_ALLOC(newvar->key); + + newvar->value = git__strdup(value); + GITERR_CHECK_ALLOC(newvar->value); + + var->next = newvar; + } + + result = config_write(b, key, &preg, value); + + git__free(key); + regfree(&preg); + + return result; +} + +static int config_delete(git_config_file *cfg, const char *name) +{ + cvar_t *var; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + int result; + khiter_t pos; + + if (normalize_name(name, &key) < 0) + return -1; + + pos = git_strmap_lookup_index(b->values, key); + git__free(key); + + if (!git_strmap_valid_index(b->values, pos)) { + giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); + return GIT_ENOTFOUND; + } + + var = git_strmap_value_at(b->values, pos); + + if (var->next != NULL) { + giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete"); + return -1; + } + + git_strmap_delete_at(b->values, pos); + + result = config_write(b, var->key, NULL, NULL); + + cvar_free(var); + return result; } int git_config_file__ondisk(git_config_file **out, const char *path) @@ -389,26 +468,25 @@ int git_config_file__ondisk(git_config_file **out, const char *path) diskfile_backend *backend; backend = git__malloc(sizeof(diskfile_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); memset(backend, 0x0, sizeof(diskfile_backend)); backend->file_path = git__strdup(path); - if (backend->file_path == NULL) { - free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->file_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.free = backend_free; *out = (git_config_file *)backend; - return GIT_SUCCESS; + return 0; } static int cfg_getchar_raw(diskfile_backend *cfg) @@ -449,7 +527,8 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags) assert(cfg_file->reader.read_ptr); do c = cfg_getchar_raw(cfg_file); - while (skip_whitespace && isspace(c)); + while (skip_whitespace && git__isspace(c) && + !cfg_file->reader.eof); if (skip_comments && (c == '#' || c == ';')) { do c = cfg_getchar_raw(cfg_file); @@ -486,21 +565,23 @@ 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) +static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) { char *line = NULL; char *line_src, *line_end; - int line_len; + size_t line_len; line_src = cfg->reader.read_ptr; - /* Skip empty empty lines */ - while (isspace(*line_src)) - ++line_src; + if (skip_whitespace) { + /* Skip empty empty lines */ + while (git__isspace(*line_src)) + ++line_src; + } - line_end = strchr(line_src, '\n'); + line_end = strchr(line_src, '\n'); - /* no newline at EOF */ + /* no newline at EOF */ if (line_end == NULL) line_end = strchr(line_src, 0); @@ -512,10 +593,8 @@ static char *cfg_readline(diskfile_backend *cfg) memcpy(line, line_src, line_len); - line[line_len] = '\0'; - - while (--line_len >= 0 && isspace(line[line_len])) - line[line_len] = '\0'; + do line[line_len] = '\0'; + while (line_len-- > 0 && git__isspace(line[line_len])); if (*line_end == '\n') line_end++; @@ -532,7 +611,7 @@ static char *cfg_readline(diskfile_backend *cfg) /* * Consume a line, without storing it anywhere */ -void cfg_consume_line(diskfile_backend *cfg) +static void cfg_consume_line(diskfile_backend *cfg) { char *line_start, *line_end; @@ -558,12 +637,11 @@ GIT_INLINE(int) config_keychar(int c) return isalnum(c) || c == '-'; } -static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) +static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name) { - int buf_len, total_len, pos, rpos; - int c, ret; - char *subsection, *first_quote, *last_quote; - int error = GIT_SUCCESS; + 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 @@ -574,16 +652,14 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha first_quote = strchr(line, '"'); last_quote = strrchr(line, '"'); - if (last_quote - first_quote == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark"); + if (last_quote - first_quote == 0) { + set_parse_error(cfg, 0, "Missing closing quotation mark in section header"); + return -1; + } - buf_len = last_quote - first_quote + 2; + git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2); + git_buf_printf(&buf, "%s.", base_name); - subsection = git__malloc(buf_len + 2); - if (subsection == NULL) - return GIT_ENOMEM; - - pos = 0; rpos = 0; quote_marks = 0; @@ -596,139 +672,123 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha */ do { if (quote_marks == 2) { - error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote"); - goto out; - + set_parse_error(cfg, rpos, "Unexpected text after closing quotes"); + git_buf_free(&buf); + return -1; } switch (c) { case '"': ++quote_marks; - break; + continue; + case '\\': c = line[rpos++]; + switch (c) { case '"': case '\\': break; + default: - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c); - goto out; + set_parse_error(cfg, rpos, "Unsupported escape sequence"); + git_buf_free(&buf); + return -1; } - break; + default: break; } - subsection[pos++] = (char) c; + git_buf_putc(&buf, c); } while ((c = line[rpos++]) != ']'); - subsection[pos] = '\0'; - - total_len = strlen(base_name) + strlen(subsection) + 2; - *section_name = git__malloc(total_len); - if (*section_name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); - if (ret >= total_len) { - /* If this fails, we've checked the length wrong */ - error = git__throw(GIT_ERROR, "Failed to parse ext header. Wrong total length calculation"); - goto out; - } else if (ret < 0) { - error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno)); - goto out; - } - - git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); - - out: - free(subsection); - - return error; + *section_name = git_buf_detach(&buf); + return 0; } static int parse_section_header(diskfile_backend *cfg, char **section_out) { char *name, *name_end; int name_length, c, pos; - int error = GIT_SUCCESS; + int result; char *line; - line = cfg_readline(cfg); + line = cfg_readline(cfg, true); if (line == NULL) - return GIT_ENOMEM; + return -1; /* find the end of the variable's name */ name_end = strchr(line, ']'); - if (name_end == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end"); + if (name_end == NULL) { + git__free(line); + set_parse_error(cfg, 0, "Missing ']' in section header"); + return -1; + } name = (char *)git__malloc((size_t)(name_end - line) + 1); - if (name == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(name); name_length = 0; pos = 0; /* Make sure we were given a section header */ c = line[pos++]; - if (c != '[') { - error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug"); - goto error; - } + assert(c == '['); c = line[pos++]; do { - if (cfg->reader.eof){ - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly"); - goto error; - } - - if (isspace(c)){ + if (git__isspace(c)){ name[name_length] = '\0'; - error = parse_section_header_ext(line, name, section_out); - free(line); - free(name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header"); + result = parse_section_header_ext(cfg, line, name, section_out); + git__free(line); + git__free(name); + return result; } if (!config_keychar(c) && c != '.') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header"); - goto error; + set_parse_error(cfg, pos, "Unexpected character in header"); + goto fail_parse; } name[name_length++] = (char) tolower(c); } while ((c = line[pos++]) != ']'); - name[name_length] = 0; - free(line); - git__strtolower(name); - *section_out = name; - return GIT_SUCCESS; + if (line[pos - 1] != ']') { + set_parse_error(cfg, pos, "Unexpected end of file"); + goto fail_parse; + } -error: - free(line); - free(name); - return error; + git__free(line); + + name[name_length] = 0; + *section_out = name; + + return 0; + +fail_parse: + git__free(line); + git__free(name); + return -1; } static int skip_bom(diskfile_backend *cfg) { - static const char *utf8_bom = "\xef\xbb\xbf"; + static const char utf8_bom[] = "\xef\xbb\xbf"; + + if (cfg->reader.buffer.size < sizeof(utf8_bom)) + return 0; if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) cfg->reader.read_ptr += sizeof(utf8_bom); - /* TODO: the reference implementation does pretty stupid + /* TODO: the reference implementation does pretty stupid shit with the BoM */ - return GIT_SUCCESS; + return 0; } /* @@ -770,9 +830,9 @@ static int skip_bom(diskfile_backend *cfg) boolean_false = "no" | "0" | "false" | "off" */ -static void strip_comments(char *line) +static int strip_comments(char *line, int in_quotes) { - int quote_count = 0; + int quote_count = in_quotes; char *ptr; for (ptr = line; *ptr; ++ptr) { @@ -785,41 +845,49 @@ static void strip_comments(char *line) } } - if (isspace(ptr[-1])) { - /* TODO skip whitespace */ + /* skip any space at the end */ + if (git__isspace(ptr[-1])) { + ptr--; } + ptr[0] = '\0'; + + return quote_count; } static int config_parse(diskfile_backend *cfg_file) { - int error = GIT_SUCCESS, c; + int c; char *current_section = NULL; char *var_name; char *var_value; - cvar_t *var; + cvar_t *var, *existing; + git_buf buf = GIT_BUF_INIT; + int result = 0; + khiter_t pos; /* Initialize the reading position */ - cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; + cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; cfg_file->reader.eof = 0; /* If the file is empty, there's nothing for us to do */ if (*cfg_file->reader.read_ptr == '\0') - return GIT_SUCCESS; + return 0; skip_bom(cfg_file); - while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + while (result == 0 && !cfg_file->reader.eof) { c = cfg_peek(cfg_file, SKIP_WHITESPACE); switch (c) { - case '\0': /* We've arrived at the end of the file */ + case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */ + cfg_file->reader.eof = 1; break; case '[': /* section header, new section begins */ - free(current_section); + git__free(current_section); current_section = NULL; - error = parse_section_header(cfg_file, ¤t_section); + result = parse_section_header(cfg_file, ¤t_section); break; case ';': @@ -828,129 +896,376 @@ static int config_parse(diskfile_backend *cfg_file) break; default: /* assume variable declaration */ - error = parse_variable(cfg_file, &var_name, &var_value); - - if (error < GIT_SUCCESS) + result = parse_variable(cfg_file, &var_name, &var_value); + if (result < 0) break; - var = malloc(sizeof(cvar_t)); - if (var == NULL) { - error = GIT_ENOMEM; - break; - } + var = git__malloc(sizeof(cvar_t)); + GITERR_CHECK_ALLOC(var); memset(var, 0x0, sizeof(cvar_t)); - var->section = git__strdup(current_section); - if (var->section == NULL) { - error = GIT_ENOMEM; - free(var); - break; - } + git__strtolower(var_name); + git_buf_printf(&buf, "%s.%s", current_section, var_name); + git__free(var_name); - var->name = var_name; + if (git_buf_oom(&buf)) + return -1; + + var->key = git_buf_detach(&buf); var->value = var_value; - git__strtolower(var->name); - CVAR_LIST_APPEND(&cfg_file->var_list, var); + /* Add or append the new config option */ + pos = git_strmap_lookup_index(cfg_file->values, var->key); + if (!git_strmap_valid_index(cfg_file->values, pos)) { + git_strmap_insert(cfg_file->values, var->key, var, result); + if (result < 0) + break; + result = 0; + } else { + existing = git_strmap_value_at(cfg_file->values, pos); + while (existing->next != NULL) { + existing = existing->next; + } + existing->next = var; + } break; } } - free(current_section); + git__free(current_section); + return result; +} - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config"); +static int write_section(git_filebuf *file, const char *key) +{ + int result; + const char *dot; + git_buf buf = GIT_BUF_INIT; + + /* All of this just for [section "subsection"] */ + dot = strchr(key, '.'); + git_buf_putc(&buf, '['); + if (dot == NULL) { + git_buf_puts(&buf, key); + } else { + git_buf_put(&buf, key, dot - key); + /* TODO: escape */ + git_buf_printf(&buf, " \"%s\"", dot + 1); + } + git_buf_puts(&buf, "]\n"); + + if (git_buf_oom(&buf)) + return -1; + + result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); + git_buf_free(&buf); + + return result; +} + +/* + * This is pretty much the parsing, except we write out anything we don't have + */ +static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value) +{ + 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; + char *current_section = NULL, *section, *name, *ldot; + git_filebuf file = GIT_FILEBUF_INIT; + + /* We need to read in our own config file */ + result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path); + + /* Initialise the reading position */ + if (result == GIT_ENOTFOUND) { + cfg->reader.read_ptr = NULL; + cfg->reader.eof = 1; + data_start = NULL; + git_buf_clear(&cfg->reader.buffer); + } else if (result == 0) { + cfg->reader.read_ptr = cfg->reader.buffer.ptr; + cfg->reader.eof = 0; + data_start = cfg->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; + + skip_bom(cfg); + ldot = strrchr(key, '.'); + name = ldot + 1; + section = git__strndup(key, ldot - key); + + while (!cfg->reader.eof) { + c = cfg_peek(cfg, SKIP_WHITESPACE); + + if (c == '\0') { /* We've arrived at the end of the file */ + break; + + } else if (c == '[') { /* section header, new section begins */ + /* + * We set both positions to the current one in case we + * need to add a variable to the end of a section. In that + * case, we want both variables to point just before the + * 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; + + git__free(current_section); + current_section = NULL; + if (parse_section_header(cfg, ¤t_section) < 0) + goto rewrite_fail; + + /* Keep track of when it stops matching */ + last_section_matched = section_matches; + section_matches = !strcmp(current_section, section); + } + + else if (c == ';' || c == '#') { + cfg_consume_line(cfg); + } + + else { + /* + * If the section doesn't match, but the last section did, + * it means we need to add a variable (so skip the line + * otherwise). If both the section and name match, we need + * to overwrite the variable (so skip the line + * otherwise). pre_end needs to be updated each time so we + * don't loose that information, but we only need to + * update post_start if we're going to use it in this + * iteration. + */ + if (!section_matches) { + if (!last_section_matched) { + cfg_consume_line(cfg); + 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) + goto rewrite_fail; + + /* First try to match the name of the variable */ + if (strcasecmp(name, var_name) == 0) + has_matched = 1; + + /* If the name matches, and we have a regex to match the + * value, try to match it */ + if (has_matched && preg != NULL) + has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0); + + git__free(var_name); + git__free(var_value); + + /* if there is no match, keep going */ + if (!has_matched) + continue; + + post_start = cfg->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); + 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); + } + + /* multiline variable? we need to keep reading lines to match */ + if (preg != NULL) { + data_start = post_start; + continue; + } + + write_trailer = 1; + break; /* break from the loop */ + } + } + + /* + * Being here can mean that + * + * 1) our section is the last one in the file and we're + * adding a variable + * + * 2) we didn't find a section for us so we need to create it + * ourselves. + * + * 3) we're setting a multivar with a regex, which means we + * continue to search for matching values + * + * In the last case, if we've already replaced a value, we + * want to write the rest of the file. Otherwise we need to write + * out the whole file and then the new variable. + */ + if (write_trailer) { + /* Write out rest of the file */ + git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start)); + } else { + if (preg_replaced) { + git_filebuf_printf(&file, "\n%s", data_start); + } else { + git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); + + /* And now if we just need to add a variable */ + if (!section_matches && write_section(&file, section) < 0) + goto rewrite_fail; + + /* Sanity check: if we are here, and value is NULL, that means that somebody + * touched the config file after our intial read. We should probably assert() + * this, but instead we'll handle it gracefully with an error. */ + if (value == NULL) { + giterr_set(GITERR_CONFIG, + "Race condition when writing a config file (a cvar has been removed)"); + goto rewrite_fail; + } + + git_filebuf_printf(&file, "\t%s = %s\n", name, value); + } + } + + git__free(section); + git__free(current_section); + + result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); + git_buf_free(&cfg->reader.buffer); + return result; + +rewrite_fail: + git__free(section); + git__free(current_section); + + git_filebuf_cleanup(&file); + git_buf_free(&cfg->reader.buffer); + return -1; +} + +/* '\"' -> '"' etc */ +static char *fixup_line(const char *ptr, int quote_count) +{ + char *str = git__malloc(strlen(ptr) + 1); + char *out = str, *esc; + const char *escapes = "ntb\"\\"; + const char *escaped = "\n\t\b\"\\"; + + if (str == NULL) + return NULL; + + while (*ptr != '\0') { + if (*ptr == '"') { + quote_count++; + } else if (*ptr != '\\') { + *out++ = *ptr; + } else { + /* backslash, check the next char */ + ptr++; + /* if we're at the end, it's a multiline, so keep the backslash */ + if (*ptr == '\0') { + *out++ = '\\'; + goto out; + } + if ((esc = strchr(escapes, *ptr)) != NULL) { + *out++ = escaped[esc - escapes]; + } else { + git__free(str); + giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr); + return NULL; + } + } + ptr++; + } + +out: + *out = '\0'; + + return str; } static int is_multiline_var(const char *str) { - char *end = strrchr(str, '\0') - 1; - - while (isspace(*end)) - --end; - - return *end == '\\'; + const char *end = str + strlen(str); + return (end > str) && (end[-1] == '\\'); } -static int parse_multiline_variable(diskfile_backend *cfg, const char *first, char **out) +static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes) { - char *line = NULL, *end; - int error = GIT_SUCCESS, len, ret; - char *buf; + char *line = NULL, *proc_line = NULL; + int quote_count; /* Check that the next line exists */ - line = cfg_readline(cfg); + line = cfg_readline(cfg, false); if (line == NULL) - return GIT_ENOMEM; + return -1; /* We've reached the end of the file, there is input missing */ if (line[0] == '\0') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly"); - goto out; + set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var"); + git__free(line); + return -1; } - strip_comments(line); + quote_count = strip_comments(line, !!in_quotes); /* If it was just a comment, pretend it didn't exist */ if (line[0] == '\0') { - error = parse_multiline_variable(cfg, first, out); - goto out; + git__free(line); + return parse_multiline_variable(cfg, value, quote_count); + /* TODO: unbounded recursion. This **could** be exploitable */ } - /* Find the continuation character '\' and strip the whitespace */ - end = strrchr(first, '\\'); - while (isspace(end[-1])) - --end; + /* Drop the continuation character '\': to closely follow the UNIX + * 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); - *end = '\0'; /* Terminate the string here */ - - len = strlen(first) + strlen(line) + 2; - buf = git__malloc(len); - if (buf == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = snprintf(buf, len, "%s %s", first, line); - if (ret < 0) { - error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno)); - free(buf); - goto out; + proc_line = fixup_line(line, in_quotes); + if (proc_line == NULL) { + git__free(line); + return -1; } + /* add this line to the multiline var */ + git_buf_puts(value, proc_line); + git__free(line); + git__free(proc_line); /* - * If we need to continue reading the next line, pretend - * everything we've read up to now was in one line and call - * ourselves. + * If we need to continue reading the next line, let's just + * keep putting stuff in the buffer */ - if (is_multiline_var(buf)) { - char *final_val; - error = parse_multiline_variable(cfg, buf, &final_val); - free(buf); - buf = final_val; - } + if (is_multiline_var(value->ptr)) + return parse_multiline_variable(cfg, value, quote_count); - *out = buf; - - out: - free(line); - return error; + return 0; } static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value) { - char *tmp; - int error = GIT_SUCCESS; const char *var_end = NULL; const char *value_start = NULL; char *line; + int quote_count; - line = cfg_readline(cfg); + line = cfg_readline(cfg, true); if (line == NULL) - return GIT_ENOMEM; + return -1; - strip_comments(line); + quote_count = strip_comments(line, 0); var_end = strchr(line, '='); @@ -959,51 +1274,47 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val else value_start = var_end + 1; - if (isspace(var_end[-1])) { + if (git__isspace(var_end[-1])) { do var_end--; - while (isspace(var_end[0])); + while (git__isspace(var_end[0])); } - tmp = git__strndup(line, var_end - line + 1); - if (tmp == NULL) { - error = GIT_ENOMEM; - goto out; - } + *var_name = git__strndup(line, var_end - line + 1); + GITERR_CHECK_ALLOC(*var_name); - *var_name = tmp; + /* If there is no value, boolean true is assumed */ + *var_value = NULL; /* * Now, let's try to parse the value */ if (value_start != NULL) { - - while (isspace(value_start[0])) + while (git__isspace(value_start[0])) value_start++; - if (value_start[0] == '\0') - goto out; - if (is_multiline_var(value_start)) { - error = parse_multiline_variable(cfg, value_start, var_value); - if (error < GIT_SUCCESS) - free(*var_name); - goto out; + git_buf multi_value = GIT_BUF_INIT; + char *proc_line = fixup_line(value_start, 0); + 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)) { + git__free(*var_name); + git__free(line); + git_buf_free(&multi_value); + return -1; + } + + *var_value = git_buf_detach(&multi_value); + + } + else if (value_start[0] != '\0') { + *var_value = fixup_line(value_start, 0); + GITERR_CHECK_ALLOC(*var_value); } - tmp = strdup(value_start); - if (tmp == NULL) { - free(*var_name); - error = GIT_ENOMEM; - goto out; - } - - *var_value = tmp; - } else { - /* If there is no value, boolean true is assumed */ - *var_value = NULL; } - out: - free(line); - return error; + git__free(line); + return 0; } diff --git a/src/config_file.h b/src/config_file.h new file mode 100644 index 000000000..0080b5713 --- /dev/null +++ b/src/config_file.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_file_h__ +#define INCLUDE_config_file_h__ + +#include "git2/config.h" + +GIT_INLINE(int) git_config_file_open(git_config_file *cfg) +{ + return cfg->open(cfg); +} + +GIT_INLINE(void) git_config_file_free(git_config_file *cfg) +{ + cfg->free(cfg); +} + +GIT_INLINE(int) git_config_file_foreach( + git_config_file *cfg, + int (*fn)(const char *key, const char *value, void *data), + void *data) +{ + return cfg->foreach(cfg, fn, data); +} + +#endif + diff --git a/src/crlf.c b/src/crlf.c new file mode 100644 index 000000000..303a46d3b --- /dev/null +++ b/src/crlf.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" + +#include "git2/attr.h" + +struct crlf_attrs { + int crlf_action; + int eol; +}; + +struct crlf_filter { + git_filter f; + struct crlf_attrs attrs; +}; + +static int check_crlf(const char *value) +{ + if (GIT_ATTR_TRUE(value)) + return GIT_CRLF_TEXT; + + if (GIT_ATTR_FALSE(value)) + return GIT_CRLF_BINARY; + + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_CRLF_GUESS; + + if (strcmp(value, "input") == 0) + return GIT_CRLF_INPUT; + + if (strcmp(value, "auto") == 0) + return GIT_CRLF_AUTO; + + return GIT_CRLF_GUESS; +} + +static int check_eol(const char *value) +{ + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_EOL_UNSET; + + if (strcmp(value, "lf") == 0) + return GIT_EOL_LF; + + if (strcmp(value, "crlf") == 0) + return GIT_EOL_CRLF; + + return GIT_EOL_UNSET; +} + +static int crlf_input_action(struct crlf_attrs *ca) +{ + if (ca->crlf_action == GIT_CRLF_BINARY) + return GIT_CRLF_BINARY; + + if (ca->eol == GIT_EOL_LF) + return GIT_CRLF_INPUT; + + if (ca->eol == GIT_EOL_CRLF) + return GIT_CRLF_CRLF; + + return ca->crlf_action; +} + +static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path) +{ +#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 drop_crlf(git_buf *dest, const git_buf *source) +{ + const char *scan = source->ptr, *next; + const char *scan_end = git_buf_cstr(source) + git_buf_len(source); + + /* Main scan loop. Find the next carriage return and copy the + * whole chunk up to that point to the destination buffer. + */ + while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) { + /* copy input up to \r */ + if (next > scan) + git_buf_put(dest, scan, next - scan); + + /* Do not drop \r unless it is followed by \n */ + if (*(next + 1) != '\n') + git_buf_putc(dest, '\r'); + + scan = next + 1; + } + + /* If there was no \r, then tell the library to skip this filter */ + if (scan == source->ptr) + return -1; + + /* Copy remaining input into dest */ + git_buf_put(dest, scan, scan_end - scan); + return 0; +} + +static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) +{ + struct crlf_filter *filter = (struct crlf_filter *)self; + + assert(self && dest && source); + + /* Empty file? Nothing to do */ + if (git_buf_len(source) == 0) + 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) { + + git_text_stats stats; + git_text_gather_stats(&stats, source); + + /* + * We're currently not going to even try to convert stuff + * that has bare CR characters. Does anybody do that crazy + * stuff? + */ + if (stats.cr != stats.crlf) + return -1; + + /* + * And add some heuristics for binary vs text, of course... + */ + if (git_text_is_binary(&stats)) + return -1; + +#if 0 + if (crlf_action == 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(path)) + return 0; + } +#endif + + if (!stats.cr) + return -1; + } + + /* Actually drop the carriage returns */ + return drop_crlf(dest, source); +} + +int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +{ + struct crlf_attrs ca; + struct crlf_filter *filter; + int error; + + /* Load gitattributes for the path */ + if ((error = crlf_load_attributes(&ca, repo, path)) < 0) + return error; + + /* + * Use the core Git logic to see if we should perform CRLF for this file + * based on its attributes & the value of `core.auto_crlf` + */ + ca.crlf_action = crlf_input_action(&ca); + + if (ca.crlf_action == GIT_CRLF_BINARY) + return 0; + + if (ca.crlf_action == GIT_CRLF_GUESS) { + int auto_crlf; + + if ((error = git_repository__cvar( + &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + return error; + + if (auto_crlf == GIT_AUTO_CRLF_FALSE) + return 0; + } + + /* If we're good, we create a new filter object and push it + * into the filters array */ + filter = git__malloc(sizeof(struct crlf_filter)); + GITERR_CHECK_ALLOC(filter); + + filter->f.apply = &crlf_apply_to_odb; + filter->f.do_free = NULL; + memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); + + return git_vector_insert(filters, filter); +} + diff --git a/src/delta-apply.c b/src/delta-apply.c index a6b711436..815ca8f16 100644 --- a/src/delta-apply.c +++ b/src/delta-apply.c @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ #include "common.h" #include "git2/odb.h" #include "delta-apply.h" @@ -5,7 +11,7 @@ /* * This file was heavily cribbed from BinaryDelta.java in JGit, which * itself was heavily cribbed from patch-delta.c in the - * GIT project. The original delta patching code was written by + * GIT project. The original delta patching code was written by * Nicolas Pitre . */ @@ -45,14 +51,19 @@ int git__delta_apply( * if not we would underflow while accessing data from the * base object, resulting in data corruption or segfault. */ - if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) - return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data"); + if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } - if (hdr_sz(&res_sz, &delta, delta_end) < 0) - return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data"); + if (hdr_sz(&res_sz, &delta, delta_end) < 0) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } + + res_dp = git__malloc(res_sz + 1); + GITERR_CHECK_ALLOC(res_dp); - if ((res_dp = git__malloc(res_sz + 1)) == NULL) - return GIT_ENOMEM; res_dp[res_sz] = '\0'; out->data = res_dp; out->len = res_sz; @@ -64,15 +75,15 @@ int git__delta_apply( */ size_t off = 0, len = 0; - if (cmd & 0x01) off = *delta++; - if (cmd & 0x02) off |= *delta++ << 8; + if (cmd & 0x01) off = *delta++; + if (cmd & 0x02) off |= *delta++ << 8; if (cmd & 0x04) off |= *delta++ << 16; if (cmd & 0x08) off |= *delta++ << 24; - if (cmd & 0x10) len = *delta++; - if (cmd & 0x20) len |= *delta++ << 8; + if (cmd & 0x10) len = *delta++; + if (cmd & 0x20) len |= *delta++ << 8; if (cmd & 0x40) len |= *delta++ << 16; - if (!len) len = 0x10000; + if (!len) len = 0x10000; if (base_len < off + len || res_sz < len) goto fail; @@ -87,7 +98,7 @@ int git__delta_apply( if (delta_end - delta < cmd || res_sz < cmd) goto fail; memcpy(res_dp, delta, cmd); - delta += cmd; + delta += cmd; res_dp += cmd; res_sz -= cmd; @@ -100,10 +111,11 @@ int git__delta_apply( if (delta != delta_end || res_sz) goto fail; - return GIT_SUCCESS; + return 0; fail: - free(out->data); + git__free(out->data); out->data = NULL; - return git__throw(GIT_ERROR, "Failed to apply delta"); + giterr_set(GITERR_INVALID, "Failed to apply delta"); + return -1; } diff --git a/src/delta-apply.h b/src/delta-apply.h index 36c5cc60d..66fa76d43 100644 --- a/src/delta-apply.h +++ b/src/delta-apply.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_delta_apply_h__ #define INCLUDE_delta_apply_h__ @@ -14,7 +20,7 @@ * @param delta the delta to execute copy/insert instructions from. * @param delta_len total number of bytes in the delta. * @return - * - GIT_SUCCESS on a successful delta unpack. + * - 0 on a successful delta unpack. * - GIT_ERROR if the delta is corrupt or doesn't match the base. */ extern int git__delta_apply( diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 000000000..90baa9588 --- /dev/null +++ b/src/diff.c @@ -0,0 +1,784 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/diff.h" +#include "diff.h" +#include "fileops.h" +#include "config.h" +#include "attr_file.h" + +static char *diff_prefix_from_pathspec(const git_strarray *pathspec) +{ + git_buf prefix = GIT_BUF_INIT; + const char *scan; + + if (git_buf_common_prefix(&prefix, pathspec) < 0) + return NULL; + + /* diff prefix will only be leading non-wildcards */ + for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan); + git_buf_truncate(&prefix, scan - prefix.ptr); + + if (prefix.size > 0) + return git_buf_detach(&prefix); + + git_buf_free(&prefix); + return NULL; +} + +static bool diff_pathspec_is_interesting(const git_strarray *pathspec) +{ + const char *str; + + if (pathspec == NULL || pathspec->count == 0) + return false; + if (pathspec->count > 1) + return true; + + str = pathspec->strings[0]; + if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) + return false; + return true; +} + +static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) +{ + unsigned int i; + git_attr_fnmatch *match; + + if (!diff->pathspec.length) + return true; + + git_vector_foreach(&diff->pathspec, i, match) { + int result = p_fnmatch(match->pattern, path, 0); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + strncmp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + } + + return false; +} + +static git_diff_delta *diff_delta__alloc( + git_diff_list *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git_pool_strdup(&diff->pool, path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + + delta->new_file.path = delta->old_file.path; + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +static git_diff_delta *diff_delta__dup( + const git_diff_delta *d, git_pool *pool) +{ + git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); + if (!delta) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + + delta->old_file.path = git_pool_strdup(pool, d->old_file.path); + if (delta->old_file.path == NULL) + goto fail; + + if (d->new_file.path != d->old_file.path) { + delta->new_file.path = git_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; + } else { + delta->new_file.path = delta->old_file.path; + } + + return delta; + +fail: + git__free(delta); + return NULL; +} + +static git_diff_delta *diff_delta__merge_like_cgit( + const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) +{ + git_diff_delta *dup = diff_delta__dup(a, pool); + if (!dup) + return NULL; + + if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0) + return dup; + + git_oid_cpy(&dup->new_file.oid, &b->new_file.oid); + + dup->new_file.mode = b->new_file.mode; + dup->new_file.size = b->new_file.size; + dup->new_file.flags = b->new_file.flags; + + /* Emulate C git for merging two diffs (a la 'git diff '). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + */ + if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) { + if (dup->status == GIT_DELTA_DELETED) + /* preserve pending delete info */; + else if (b->status == GIT_DELTA_UNTRACKED || + b->status == GIT_DELTA_IGNORED) + dup->status = b->status; + else + dup->status = GIT_DELTA_UNMODIFIED; + } + else if (dup->status == GIT_DELTA_UNMODIFIED || + b->status == GIT_DELTA_DELETED) + dup->status = b->status; + + return dup; +} + +static int diff_delta__from_one( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *entry) +{ + git_diff_delta *delta; + + if (status == GIT_DELTA_IGNORED && + (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return 0; + + if (status == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return 0; + + if (!diff_path_matches_pathspec(diff, entry->path)) + return 0; + + delta = diff_delta__alloc(diff, status, entry->path); + GITERR_CHECK_ALLOC(delta); + + /* This fn is just for single-sided diffs */ + assert(status != GIT_DELTA_MODIFIED); + + if (delta->status == GIT_DELTA_DELETED) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + git_oid_cpy(&delta->old_file.oid, &entry->oid); + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + git_oid_cpy(&delta->new_file.oid, &entry->oid); + } + + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } + + return 0; +} + +static int diff_delta__from_two( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *old_entry, + const git_index_entry *new_entry, + git_oid *new_oid) +{ + git_diff_delta *delta; + + if (status == GIT_DELTA_UNMODIFIED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return 0; + + if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { + const git_index_entry *temp = old_entry; + old_entry = new_entry; + new_entry = temp; + } + + delta = diff_delta__alloc(diff, status, old_entry->path); + GITERR_CHECK_ALLOC(delta); + + delta->old_file.mode = old_entry->mode; + git_oid_cpy(&delta->old_file.oid, &old_entry->oid); + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + + delta->new_file.mode = new_entry->mode; + git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); + if (new_oid || !git_oid_iszero(&new_entry->oid)) + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } + + return 0; +} + +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) +{ + size_t len = strlen(prefix); + + /* append '/' at end if needed */ + if (len > 0 && prefix[len - 1] != '/') + return git_pool_strcat(pool, prefix, "/"); + else + return git_pool_strndup(pool, prefix, len + 1); +} + +static int diff_delta__cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(da->old_file.path, db->old_file.path); + return val ? val : ((int)da->status - (int)db->status); +} + +static int config_bool(git_config *cfg, const char *name, int defvalue) +{ + int val = defvalue; + + if (git_config_get_bool(&val, cfg, name) < 0) + giterr_clear(); + + return val; +} + +static git_diff_list *git_diff_list_alloc( + git_repository *repo, const git_diff_options *opts) +{ + git_config *cfg; + size_t i; + git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); + if (diff == NULL) + return NULL; + + diff->repo = repo; + + if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || + git_pool_init(&diff->pool, 1, 0) < 0) + goto fail; + + /* load config values that affect diff behavior */ + if (git_repository_config__weakptr(&cfg, repo) < 0) + goto fail; + if (config_bool(cfg, "core.symlinks", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; + if (config_bool(cfg, "core.ignorestat", 0)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; + if (config_bool(cfg, "core.filemode", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT; + if (config_bool(cfg, "core.trustctime", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; + /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ + + if (opts == NULL) + return diff; + + memcpy(&diff->opts, opts, sizeof(git_diff_options)); + memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); + + diff->opts.old_prefix = diff_strdup_prefix(&diff->pool, + opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); + diff->opts.new_prefix = diff_strdup_prefix(&diff->pool, + opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT); + + if (!diff->opts.old_prefix || !diff->opts.new_prefix) + goto fail; + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + char *swap = diff->opts.old_prefix; + diff->opts.old_prefix = diff->opts.new_prefix; + diff->opts.new_prefix = swap; + } + + /* only copy pathspec if it is "interesting" so we can test + * diff->pathspec.length > 0 to know if it is worth calling + * fnmatch as we iterate. + */ + if (!diff_pathspec_is_interesting(&opts->pathspec)) + return diff; + + if (git_vector_init( + &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) + goto fail; + + for (i = 0; i < opts->pathspec.count; ++i) { + int ret; + const char *pattern = opts->pathspec.strings[i]; + git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + goto fail; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); + if (ret == GIT_ENOTFOUND) { + git__free(match); + continue; + } else if (ret < 0) + goto fail; + + if (git_vector_insert(&diff->pathspec, match) < 0) + goto fail; + } + + return diff; + +fail: + git_diff_list_free(diff); + return NULL; +} + +void git_diff_list_free(git_diff_list *diff) +{ + git_diff_delta *delta; + git_attr_fnmatch *match; + unsigned int i; + + if (!diff) + return; + + git_vector_foreach(&diff->deltas, i, delta) { + git__free(delta); + diff->deltas.contents[i] = NULL; + } + git_vector_free(&diff->deltas); + + git_vector_foreach(&diff->pathspec, i, match) { + git__free(match); + diff->pathspec.contents[i] = NULL; + } + git_vector_free(&diff->pathspec); + + git_pool_clear(&diff->pool); + git__free(diff); +} + +static int oid_for_workdir_item( + git_repository *repo, + const git_index_entry *item, + git_oid *oid) +{ + int result; + git_buf full_path = GIT_BUF_INIT; + + if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0) + return -1; + + /* calculate OID for file if possible*/ + if (S_ISLNK(item->mode)) + result = git_odb__hashlink(oid, full_path.ptr); + else if (!git__is_sizet(item->file_size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + result = -1; + } else { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + result = fd; + else { + result = git_odb__hashfd( + oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); + p_close(fd); + } + } + + git_buf_free(&full_path); + + return result; +} + +#define EXEC_BIT_MASK 0000111 + +static int maybe_modified( + git_iterator *old_iter, + const git_index_entry *oitem, + git_iterator *new_iter, + const git_index_entry *nitem, + git_diff_list *diff) +{ + git_oid noid, *use_noid = NULL; + git_delta_t status = GIT_DELTA_MODIFIED; + unsigned int omode = oitem->mode; + unsigned int nmode = nitem->mode; + + GIT_UNUSED(old_iter); + + if (!diff_path_matches_pathspec(diff, oitem->path)) + return 0; + + /* on platforms with no symlinks, promote plain files to symlinks */ + if (S_ISLNK(omode) && S_ISREG(nmode) && + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK); + + /* on platforms with no execmode, clear exec bit from comparisons */ + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) { + omode = omode & ~EXEC_BIT_MASK; + nmode = nmode & ~EXEC_BIT_MASK; + } + + /* support "assume unchanged" (badly, 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; + + /* support "skip worktree" index bit */ + else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) + status = GIT_DELTA_UNMODIFIED; + + /* if basic type of file changed, then split into delete and add */ + else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || + diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0) + return -1; + return 0; + } + + /* if oids and modes match, then file is unmodified */ + else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && + omode == nmode) + status = GIT_DELTA_UNMODIFIED; + + /* if we have a workdir item with an unknown oid, check deeper */ + else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) { + /* TODO: add check against index file st_mtime to avoid racy-git */ + + /* if they files look exactly alike, then we'll assume the same */ + if (oitem->file_size == nitem->file_size && + (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || + (oitem->ctime.seconds == nitem->ctime.seconds)) && + oitem->mtime.seconds == nitem->mtime.seconds && + (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) || + (oitem->dev == nitem->dev)) && + oitem->ino == nitem->ino && + oitem->uid == nitem->uid && + oitem->gid == nitem->gid) + status = GIT_DELTA_UNMODIFIED; + + else if (S_ISGITLINK(nmode)) { + git_submodule *sub; + + if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0) + status = GIT_DELTA_UNMODIFIED; + else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) + return -1; + else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) + status = GIT_DELTA_UNMODIFIED; + else { + /* TODO: support other GIT_SUBMODULE_IGNORE values */ + status = GIT_DELTA_UNMODIFIED; + } + } + + /* TODO: check git attributes so we will not have to read the file + * in if it is marked binary. + */ + + else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) + return -1; + + else if (git_oid_cmp(&oitem->oid, &noid) == 0 && + omode == nmode) + status = GIT_DELTA_UNMODIFIED; + + /* store calculated oid so we don't have to recalc later */ + use_noid = &noid; + } + + return diff_delta__from_two(diff, status, oitem, nitem, use_noid); +} + +static int diff_from_iterators( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_iterator *old_iter, + git_iterator *new_iter, + git_diff_list **diff_ptr) +{ + const git_index_entry *oitem, *nitem; + git_buf ignore_prefix = GIT_BUF_INIT; + git_diff_list *diff = git_diff_list_alloc(repo, opts); + if (!diff) + goto fail; + + diff->old_src = old_iter->type; + diff->new_src = new_iter->type; + + if (git_iterator_current(old_iter, &oitem) < 0 || + git_iterator_current(new_iter, &nitem) < 0) + goto fail; + + /* run iterators building diffs */ + while (oitem || nitem) { + + /* create DELETED records for old items not matched in new */ + if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) { + if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || + git_iterator_advance(old_iter, &oitem) < 0) + goto fail; + } + + /* create ADDED, TRACKED, or IGNORED records for new items not + * matched in old (and/or descend into directories as needed) + */ + else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { + git_delta_t delta_type = GIT_DELTA_UNTRACKED; + + /* check if contained in ignored parent directory */ + if (git_buf_len(&ignore_prefix) && + git__prefixcmp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0) + delta_type = GIT_DELTA_IGNORED; + + if (S_ISDIR(nitem->mode)) { + /* recurse into directory only if there are tracked items in + * it or if the user requested the contents of untracked + * directories and it is not under an ignored directory. + */ + if ((oitem && git__prefixcmp(oitem->path, nitem->path) == 0) || + (delta_type == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0)) + { + /* if this directory is ignored, remember it as the + * "ignore_prefix" for processing contained items + */ + if (delta_type == GIT_DELTA_UNTRACKED && + git_iterator_current_is_ignored(new_iter)) + git_buf_sets(&ignore_prefix, nitem->path); + + if (git_iterator_advance_into_directory(new_iter, &nitem) < 0) + goto fail; + + continue; + } + } + + /* In core git, the next two "else if" clauses are effectively + * reversed -- i.e. when an untracked file contained in an + * ignored directory is individually ignored, it shows up as an + * ignored file in the diff list, even though other untracked + * files in the same directory are skipped completely. + * + * To me, this is odd. If the directory is ignored and the file + * is untracked, we should skip it consistently, regardless of + * whether it happens to match a pattern in the ignore file. + * + * To match the core git behavior, just reverse the following + * two "else if" cases so that individual file ignores are + * checked before container directory exclusions are used to + * skip the file. + */ + else if (delta_type == GIT_DELTA_IGNORED) { + if (git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + continue; /* ignored parent directory, so skip completely */ + } + + else if (git_iterator_current_is_ignored(new_iter)) + delta_type = GIT_DELTA_IGNORED; + + else if (new_iter->type != GIT_ITERATOR_WORKDIR) + delta_type = GIT_DELTA_ADDED; + + if (diff_delta__from_one(diff, delta_type, nitem) < 0 || + git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + } + + /* otherwise item paths match, so create MODIFIED record + * (or ADDED and DELETED pair if type changed) + */ + else { + assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); + + if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || + git_iterator_advance(old_iter, &oitem) < 0 || + git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + } + } + + git_iterator_free(old_iter); + git_iterator_free(new_iter); + git_buf_free(&ignore_prefix); + + *diff_ptr = diff; + return 0; + +fail: + git_iterator_free(old_iter); + git_iterator_free(new_iter); + git_buf_free(&ignore_prefix); + + git_diff_list_free(diff); + *diff_ptr = NULL; + return -1; +} + + +int git_diff_tree_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_tree *new_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && old_tree && new_tree && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_index_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_workdir_to_index( + git_repository *repo, + const git_diff_options *opts, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && diff); + + if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 || + git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + + +int git_diff_workdir_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && old_tree && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_merge( + git_diff_list *onto, + const git_diff_list *from) +{ + int error = 0; + git_pool onto_pool; + git_vector onto_new; + git_diff_delta *delta; + unsigned int i, j; + + assert(onto && from); + + if (!from->deltas.length) + return 0; + + if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1, 0) < 0) + return -1; + + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { + git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); + const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); + int cmp = !f ? -1 : !o ? 1 : strcmp(o->old_file.path, f->old_file.path); + + if (cmp < 0) { + delta = diff_delta__dup(o, &onto_pool); + i++; + } else if (cmp > 0) { + delta = diff_delta__dup(f, &onto_pool); + j++; + } else { + delta = diff_delta__merge_like_cgit(o, f, &onto_pool); + i++; + j++; + } + + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) + break; + } + + if (!error) { + git_vector_swap(&onto->deltas, &onto_new); + git_pool_swap(&onto->pool, &onto_pool); + onto->new_src = from->new_src; + } + + git_vector_foreach(&onto_new, i, delta) + git__free(delta); + git_vector_free(&onto_new); + git_pool_clear(&onto_pool); + + return error; +} + diff --git a/src/diff.h b/src/diff.h new file mode 100644 index 000000000..ac2457956 --- /dev/null +++ b/src/diff.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_diff_h__ +#define INCLUDE_diff_h__ + +#include +#include "vector.h" +#include "buffer.h" +#include "iterator.h" +#include "repository.h" +#include "pool.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_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */ + GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ + GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ +}; + +struct git_diff_list { + git_repository *repo; + git_diff_options opts; + git_vector pathspec; + git_vector deltas; /* vector of git_diff_file_delta */ + git_pool pool; + git_iterator_type_t old_src; + git_iterator_type_t new_src; + uint32_t diffcaps; +}; + +#endif + diff --git a/src/diff_output.c b/src/diff_output.c new file mode 100644 index 000000000..5ffa641c4 --- /dev/null +++ b/src/diff_output.c @@ -0,0 +1,786 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/diff.h" +#include "git2/attr.h" +#include "git2/blob.h" +#include "xdiff/xdiff.h" +#include +#include "diff.h" +#include "map.h" +#include "fileops.h" +#include "filter.h" + +typedef struct { + git_diff_list *diff; + void *cb_data; + git_diff_hunk_fn hunk_cb; + git_diff_data_fn line_cb; + unsigned int index; + git_diff_delta *delta; + git_diff_range range; +} diff_output_info; + +static int read_next_int(const char **str, int *value) +{ + const char *scan = *str; + int v = 0, digits = 0; + /* find next digit */ + for (scan = *str; *scan && !isdigit(*scan); scan++); + /* parse next number */ + for (; isdigit(*scan); scan++, digits++) + v = (v * 10) + (*scan - '0'); + *str = scan; + *value = v; + return (digits > 0) ? 0 : -1; +} + +static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) +{ + diff_output_info *info = priv; + + if (len == 1 && info->hunk_cb) { + git_diff_range range = { -1, 0, -1, 0 }; + const char *scan = bufs[0].ptr; + + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (*scan != '@') + return -1; + + if (read_next_int(&scan, &range.old_start) < 0) + return -1; + if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0) + return -1; + + if (read_next_int(&scan, &range.new_start) < 0) + return -1; + if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0) + return -1; + + if (range.old_start < 0 || range.new_start < 0) + return -1; + + memcpy(&info->range, &range, sizeof(git_diff_range)); + + return info->hunk_cb( + info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size); + } + + if ((len == 2 || len == 3) && info->line_cb) { + int origin; + + /* expect " "/"-"/"+", then data, then maybe newline */ + origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + if (info->line_cb( + info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0) + return -1; + + /* deal with adding and removing newline at EOF */ + if (len == 3) { + if (origin == GIT_DIFF_LINE_ADDITION) + origin = GIT_DIFF_LINE_ADD_EOFNL; + else + origin = GIT_DIFF_LINE_DEL_EOFNL; + + return info->line_cb( + info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size); + } + } + + return 0; +} + +#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) + +static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +{ + const char *value; + if (git_attr_get(&value, repo, 0, file->path, "diff") < 0) + return -1; + + if (GIT_ATTR_FALSE(value)) + file->flags |= GIT_DIFF_FILE_BINARY; + else if (GIT_ATTR_TRUE(value)) + file->flags |= GIT_DIFF_FILE_NOT_BINARY; + /* otherwise leave file->flags alone */ + + return 0; +} + +static void update_delta_is_binary(git_diff_delta *delta) +{ + if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || + (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) + delta->binary = 1; + else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 || + (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) + delta->binary = 0; + /* otherwise leave delta->binary value untouched */ +} + +static int file_is_binary_by_attr( + git_diff_list *diff, + git_diff_delta *delta) +{ + int error = 0, mirror_new; + + delta->binary = -1; + + /* make sure files are conceivably mmap-able */ + if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size || + (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size) + { + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + delta->binary = 1; + return 0; + } + + /* check if user is forcing us to text diff these files */ + if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) { + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->binary = 0; + return 0; + } + + /* check diff attribute +, -, or 0 */ + if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0) + return -1; + + mirror_new = (delta->new_file.path == delta->old_file.path || + strcmp(delta->new_file.path, delta->old_file.path) == 0); + if (mirror_new) + delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS); + else + error = update_file_is_binary_by_attr(diff->repo, &delta->new_file); + + update_delta_is_binary(delta); + + return error; +} + +static int file_is_binary_by_content( + git_diff_delta *delta, + git_map *old_data, + git_map *new_data) +{ + git_buf search; + + if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = old_data->data; + search.size = min(old_data->len, 4000); + + if (git_buf_is_binary(&search)) + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = new_data->data; + search.size = min(new_data->len, 4000); + + if (git_buf_is_binary(&search)) + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + update_delta_is_binary(delta); + + /* TODO: if value != NULL, implement diff drivers */ + + return 0; +} + +static void setup_xdiff_options( + git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param) +{ + memset(cfg, 0, sizeof(xdemitconf_t)); + memset(param, 0, sizeof(xpparam_t)); + + cfg->ctxlen = + (!opts || !opts->context_lines) ? 3 : opts->context_lines; + cfg->interhunkctxlen = + (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines; + + if (!opts) + return; + + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE) + param->flags |= XDF_WHITESPACE_FLAGS; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + param->flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; +} + +static int get_blob_content( + git_repository *repo, + const git_oid *oid, + git_map *map, + git_blob **blob) +{ + if (git_oid_iszero(oid)) + return 0; + + if (git_blob_lookup(blob, repo, oid) < 0) + return -1; + + map->data = (void *)git_blob_rawcontent(*blob); + map->len = git_blob_rawsize(*blob); + return 0; +} + +static int get_workdir_content( + git_repository *repo, + git_diff_file *file, + git_map *map) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + + if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0) + return -1; + + if (S_ISLNK(file->mode)) { + ssize_t read_len; + + file->flags |= GIT_DIFF_FILE_FREE_DATA; + file->flags |= GIT_DIFF_FILE_BINARY; + + map->data = git__malloc((size_t)file->size + 1); + GITERR_CHECK_ALLOC(map->data); + + read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1); + if (read_len != (ssize_t)file->size) { + giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); + error = -1; + } else + map->len = read_len; + } + else { + error = git_futils_mmap_ro_file(map, path.ptr); + file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + } + git_buf_free(&path); + return error; +} + +static void release_content(git_diff_file *file, git_map *map, git_blob *blob) +{ + if (blob != NULL) + git_blob_free(blob); + + if (file->flags & GIT_DIFF_FILE_FREE_DATA) { + git__free(map->data); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_FREE_DATA; + } + else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) { + git_futils_mmap_free(map); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA; + } +} + +static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) { + assert(dst && src); + + dst->data = src->ptr; + dst->len = src->size; +#ifdef GIT_WIN32 + dst->fmh = NULL; +#endif +} + +int git_diff_foreach( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error = 0; + diff_output_info info; + git_diff_delta *delta; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + + info.diff = diff; + info.cb_data = data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + git_vector_foreach(&diff->deltas, info.index, delta) { + git_blob *old_blob = NULL, *new_blob = NULL; + git_map old_data, new_data; + mmfile_t old_xdiff_data, new_xdiff_data; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + continue; + + if (delta->status == GIT_DELTA_IGNORED && + (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + continue; + + if (delta->status == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + continue; + + if ((error = file_is_binary_by_attr(diff, delta)) < 0) + goto cleanup; + + old_data.data = ""; + old_data.len = 0; + new_data.data = ""; + new_data.len = 0; + + /* TODO: Partial blob reading to defer loading whole blob. + * I.e. I want a blob with just the first 4kb loaded, then + * later on I will read the rest of the blob if needed. + */ + + /* map files */ + if (delta->binary != 1 && + (hunk_cb || line_cb) && + (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->old_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->old_file, &old_data); + else + error = get_blob_content( + diff->repo, &delta->old_file.oid, &old_data, &old_blob); + + if (error < 0) + goto cleanup; + } + + if (delta->binary != 1 && + (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) && + (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->new_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->new_file, &new_data); + else + error = get_blob_content( + diff->repo, &delta->new_file.oid, &new_data, &new_blob); + + if (error < 0) + goto cleanup; + + if ((delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0) { + error = git_odb_hash( + &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB); + + if (error < 0) + goto cleanup; + + /* since we did not have the definitive oid, we may have + * incorrect status and need to skip this item. + */ + if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) { + delta->status = GIT_DELTA_UNMODIFIED; + if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + goto cleanup; + } + } + } + + /* if we have not already decided whether file is binary, + * check the first 4K for nul bytes to decide... + */ + if (delta->binary == -1) { + error = file_is_binary_by_content( + delta, &old_data, &new_data); + if (error < 0) + goto cleanup; + } + + /* TODO: if ignore_whitespace is set, then we *must* do text + * diffs to tell if a file has really been changed. + */ + + if (file_cb != NULL) { + error = file_cb(data, delta, (float)info.index / diff->deltas.length); + if (error < 0) + goto cleanup; + } + + /* don't do hunk and line diffs if file is binary */ + if (delta->binary == 1) + goto cleanup; + + /* nothing to do if we did not get data */ + if (!old_data.len && !new_data.len) + goto cleanup; + + assert(hunk_cb || line_cb); + + info.delta = delta; + old_xdiff_data.ptr = old_data.data; + old_xdiff_data.size = old_data.len; + new_xdiff_data.ptr = new_data.data; + new_xdiff_data.size = new_data.len; + + xdl_diff(&old_xdiff_data, &new_xdiff_data, + &xdiff_params, &xdiff_config, &xdiff_callback); + +cleanup: + release_content(&delta->old_file, &old_data, old_blob); + release_content(&delta->new_file, &new_data, new_blob); + + if (error < 0) + break; + } + + return error; +} + + +typedef struct { + git_diff_list *diff; + git_diff_data_fn print_cb; + void *cb_data; + git_buf *buf; +} diff_print_info; + +static char pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (mode & 0100) + /* in git, modes are very regular, so we must have 0100755 mode */ + return '*'; + else + return ' '; +} + +static int print_compact(void *data, git_diff_delta *delta, float progress) +{ + diff_print_info *pi = data; + char code, old_suffix, new_suffix; + + GIT_UNUSED(progress); + + switch (delta->status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + default: code = 0; + } + + if (!code) + return 0; + + old_suffix = pick_suffix(delta->old_file.mode); + new_suffix = pick_suffix(delta->new_file.mode); + + git_buf_clear(pi->buf); + + if (delta->old_file.path != delta->new_file.path && + strcmp(delta->old_file.path,delta->new_file.path) != 0) + git_buf_printf(pi->buf, "%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(pi->buf, "%c\t%s%c (%o -> %o)\n", code, + delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); + else if (old_suffix != ' ') + git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + else + git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); + + if (git_buf_oom(pi->buf)) + return -1; + + return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); +} + +int git_diff_print_compact( + git_diff_list *diff, + void *cb_data, + git_diff_data_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL); + + git_buf_free(&buf); + + return error; +} + + +static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) +{ + char start_oid[8], end_oid[8]; + + /* TODO: Determine a good actual OID range to print */ + git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid); + git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid); + + /* TODO: Match git diff more closely */ + if (delta->old_file.mode == delta->new_file.mode) { + git_buf_printf(pi->buf, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); + } else { + if (delta->old_file.mode == 0) { + git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); + } else if (delta->new_file.mode == 0) { + git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); + } else { + git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); + git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); + } + git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); + } + + if (git_buf_oom(pi->buf)) + return -1; + + return 0; +} + +static int print_patch_file(void *data, git_diff_delta *delta, float progress) +{ + diff_print_info *pi = data; + const char *oldpfx = pi->diff->opts.old_prefix; + const char *oldpath = delta->old_file.path; + const char *newpfx = pi->diff->opts.new_prefix; + const char *newpath = delta->new_file.path; + int result; + + GIT_UNUSED(progress); + + 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 (print_oid_range(pi, delta) < 0) + return -1; + + if (git_oid_iszero(&delta->old_file.oid)) { + oldpfx = ""; + oldpath = "/dev/null"; + } + if (git_oid_iszero(&delta->new_file.oid)) { + newpfx = ""; + newpath = "/dev/null"; + } + + if (delta->binary != 1) { + git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); + git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); + } + + if (git_buf_oom(pi->buf)) + return -1; + + result = pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (result < 0) + return result; + + if (delta->binary != 1) + 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)) + return -1; + + return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); +} + +static int print_patch_hunk( + void *data, + git_diff_delta *d, + git_diff_range *r, + const char *header, + size_t header_len) +{ + diff_print_info *pi = data; + + git_buf_clear(pi->buf); + if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) + return -1; + + return pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); +} + +static int print_patch_line( + void *data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, /* GIT_DIFF_LINE value from above */ + const char *content, + size_t content_len) +{ + diff_print_info *pi = data; + + 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; + + return pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); +} + +int git_diff_print_patch( + git_diff_list *diff, + void *cb_data, + git_diff_data_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach( + diff, &pi, print_patch_file, print_patch_hunk, print_patch_line); + + git_buf_free(&buf); + + return error; +} + +int git_diff_blobs( + git_blob *old_blob, + git_blob *new_blob, + git_diff_options *options, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + diff_output_info info; + git_diff_delta delta; + mmfile_t old_data, new_data; + git_map old_map, new_map; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + git_blob *new, *old; + + memset(&delta, 0, sizeof(delta)); + + new = new_blob; + old = old_blob; + + if (options && (options->flags & GIT_DIFF_REVERSE)) { + git_blob *swap = old; + old = new; + new = swap; + } + + if (old) { + old_data.ptr = (char *)git_blob_rawcontent(old); + old_data.size = git_blob_rawsize(old); + git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old)); + } else { + old_data.ptr = ""; + old_data.size = 0; + } + + if (new) { + new_data.ptr = (char *)git_blob_rawcontent(new); + new_data.size = git_blob_rawsize(new); + git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new)); + } else { + new_data.ptr = ""; + new_data.size = 0; + } + + /* populate a "fake" delta record */ + delta.status = new ? + (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : + (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); + + if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) + delta.status = GIT_DELTA_UNMODIFIED; + + delta.old_file.size = old_data.size; + delta.new_file.size = new_data.size; + + fill_map_from_mmfile(&old_map, &old_data); + fill_map_from_mmfile(&new_map, &new_data); + + if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0) + return -1; + + if (file_cb != NULL) { + int error = file_cb(cb_data, &delta, 1); + if (error < 0) + return error; + } + + /* don't do hunk and line diffs if the two blobs are identical */ + if (delta.status == GIT_DELTA_UNMODIFIED) + return 0; + + /* don't do hunk and line diffs if file is binary */ + if (delta.binary == 1) + return 0; + + info.diff = NULL; + info.delta = δ + info.cb_data = cb_data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(options, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); + + return 0; +} diff --git a/src/dir.h b/src/dir.h deleted file mode 100644 index c01c3fae7..000000000 --- a/src/dir.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef INCLUDE_dir_h__ -#define INCLUDE_dir_h__ - -#include "common.h" - -#ifndef GIT_WIN32 -# include -#endif - -#ifdef GIT_WIN32 - -struct git__dirent { - int d_ino; - char d_name[261]; -}; - -typedef struct { - HANDLE h; - WIN32_FIND_DATA f; - struct git__dirent entry; - char *dir; - int first; -} git__DIR; - -extern git__DIR *git__opendir(const char *); -extern struct git__dirent *git__readdir(git__DIR *); -extern void git__rewinddir(git__DIR *); -extern int git__closedir(git__DIR *); - -# ifndef GIT__WIN32_NO_WRAP_DIR -# define dirent git__dirent -# define DIR git__DIR -# define opendir git__opendir -# define readdir git__readdir -# define rewinddir git__rewinddir -# define closedir git__closedir -# endif - -#endif - -#endif /* INCLUDE_dir_h__ */ diff --git a/src/errors.c b/src/errors.c index 7da02b4f7..d43d7d9b5 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,113 +1,119 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" -#include "git2/thread-utils.h" /* for GIT_TLS */ -#include "thread-utils.h" /* for GIT_TLS */ - +#include "global.h" +#include "posix.h" +#include "buffer.h" #include -static GIT_TLS char g_last_error[1024]; +/******************************************** + * New error handling + ********************************************/ -static struct { - int num; - const char *str; -} error_codes[] = { - {GIT_ERROR, "Unspecified error"}, - {GIT_ENOTOID, "Input was not a properly formatted Git object id."}, - {GIT_ENOTFOUND, "Object does not exist in the scope searched."}, - {GIT_ENOMEM, "Not enough space available."}, - {GIT_EOSERR, "Consult the OS error information."}, - {GIT_EOBJTYPE, "The specified object is of invalid type"}, - {GIT_EOBJCORRUPTED, "The specified object has its data corrupted"}, - {GIT_ENOTAREPO, "The specified repository is invalid"}, - {GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"}, - {GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"}, - {GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"}, - {GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"}, - {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"}, - {GIT_EBUSY, "The queried object is currently busy"}, - {GIT_EINVALIDPATH, "The path is invalid"}, - {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"}, - {GIT_EINVALIDREFNAME, "The name of the reference is not valid"}, - {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"}, - {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"}, - {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"}, - {GIT_EINVALIDPATH, "The path is invalid" }, - {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"}, - {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"}, - {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"}, - {GIT_EEXISTS, "A reference with this name already exists"}, - {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"}, - {GIT_ENOTNUM, "The given literal is not a valid number"}, - {GIT_EAMBIGUOUSOIDPREFIX, "The given oid prefix is ambiguous"}, +static git_error g_git_oom_error = { + "Out of memory", + GITERR_NOMEMORY }; -const char *git_strerror(int num) +static void set_error(int error_class, char *string) { - size_t i; + git_error *error = &GIT_GLOBAL->error_t; - if (num == GIT_EOSERR) - return strerror(errno); - for (i = 0; i < ARRAY_SIZE(error_codes); i++) - if (num == error_codes[i].num) - return error_codes[i].str; + git__free(error->message); - return "Unknown error"; + error->message = string; + error->klass = error_class; + + GIT_GLOBAL->last_error = error; } -int git__rethrow(int error, const char *msg, ...) +void giterr_set_oom(void) { - char new_error[1024]; - char *old_error = NULL; - - va_list va; - - va_start(va, msg); - vsnprintf(new_error, sizeof(new_error), msg, va); - va_end(va); - - old_error = strdup(g_last_error); - snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error); - free(old_error); - - return error; + GIT_GLOBAL->last_error = &g_git_oom_error; } -int git__throw(int error, const char *msg, ...) +void giterr_set(int error_class, const char *string, ...) { - va_list va; + git_buf buf = GIT_BUF_INIT; + va_list arglist; - va_start(va, msg); - vsnprintf(g_last_error, sizeof(g_last_error), msg, va); - va_end(va); + int unix_error_code = 0; - return error; +#ifdef GIT_WIN32 + DWORD win32_error_code = 0; +#endif + + if (error_class == GITERR_OS) { + unix_error_code = errno; + errno = 0; + +#ifdef GIT_WIN32 + win32_error_code = GetLastError(); + SetLastError(0); +#endif + } + + va_start(arglist, string); + git_buf_vprintf(&buf, string, arglist); + va_end(arglist); + + /* automatically suffix strerror(errno) for GITERR_OS errors */ + if (error_class == GITERR_OS) { + + if (unix_error_code != 0) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, strerror(unix_error_code)); + } + +#ifdef GIT_WIN32 + else if (win32_error_code != 0) { + LPVOID lpMsgBuf = NULL; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL); + + if (lpMsgBuf) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, lpMsgBuf); + LocalFree(lpMsgBuf); + } + } +#endif + } + + if (!git_buf_oom(&buf)) + set_error(error_class, git_buf_detach(&buf)); } -const char *git_lasterror(void) +void giterr_set_str(int error_class, const char *string) { - return g_last_error; + char *message = git__strdup(string); + + if (message) + set_error(error_class, message); +} + +void giterr_set_regex(const regex_t *regex, int error_code) +{ + char error_buf[1024]; + regerror(error_code, regex, error_buf, sizeof(error_buf)); + giterr_set_str(GITERR_REGEX, error_buf); +} + +void giterr_clear(void) +{ + GIT_GLOBAL->last_error = NULL; +} + +const git_error *giterr_last(void) +{ + return GIT_GLOBAL->last_error; } diff --git a/src/fetch.c b/src/fetch.c new file mode 100644 index 000000000..96b263faa --- /dev/null +++ b/src/fetch.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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/remote.h" +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/revwalk.h" +#include "git2/indexer.h" + +#include "common.h" +#include "transport.h" +#include "remote.h" +#include "refspec.h" +#include "pack.h" +#include "fetch.h" +#include "netops.h" + +struct filter_payload { + git_remote *remote; + const git_refspec *spec; + git_odb *odb; + int found_head; +}; + +static int filter_ref__cb(git_remote_head *head, void *payload) +{ + struct filter_payload *p = payload; + + if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) { + p->found_head = 1; + } else { + /* If it doesn't match the refpec, we don't want it */ + if (!git_refspec_src_matches(p->spec, head->name)) + return 0; + + /* Don't even try to ask for the annotation target */ + if (!git__suffixcmp(head->name, "^{}")) + return 0; + } + + /* If we have the object, mark it so we don't ask for it */ + if (git_odb_exists(p->odb, &head->oid)) + head->local = 1; + else + p->remote->need_pack = 1; + + return git_vector_insert(&p->remote->refs, head); +} + +static int filter_wants(git_remote *remote) +{ + struct filter_payload p; + + git_vector_clear(&remote->refs); + + /* + * The fetch refspec can be NULL, and what this means is that the + * user didn't specify one. This is fine, as it means that we're + * not interested in any particular branch but just the remote's + * HEAD, which will be stored in FETCH_HEAD after the fetch. + */ + p.spec = git_remote_fetchspec(remote); + p.found_head = 0; + p.remote = remote; + + if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0) + return -1; + + return remote->transport->ls(remote->transport, &filter_ref__cb, &p); +} + +/* + * In this first version, we push all our refs in and start sending + * them out. When we get an ACK we hide that commit and continue + * traversing until we're done + */ +int git_fetch_negotiate(git_remote *remote) +{ + git_transport *t = remote->transport; + + if (filter_wants(remote) < 0) { + giterr_set(GITERR_NET, "Failed to filter the reference list for wants"); + return -1; + } + + /* Don't try to negotiate when we don't want anything */ + if (remote->refs.length == 0 || !remote->need_pack) + return 0; + + /* + * Now we have everything set up so we can start tell the server + * what we want and what we have. + */ + return t->negotiate_fetch(t, remote->repo, &remote->refs); +} + +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +{ + if(!remote->need_pack) + return 0; + + return remote->transport->download_pack(remote->transport, remote->repo, bytes, stats); +} + +/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */ +int git_fetch__download_pack( + const char *buffered, + size_t buffered_size, + git_transport *t, + git_repository *repo, + git_off_t *bytes, + git_indexer_stats *stats) +{ + int recvd; + char buff[1024]; + gitno_buffer buf; + git_indexer_stream *idx; + + gitno_buffer_setup(t, &buf, buff, sizeof(buff)); + + if (memcmp(buffered, "PACK", strlen("PACK"))) { + giterr_set(GITERR_NET, "The pack doesn't start with the signature"); + return -1; + } + + if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) + return -1; + + memset(stats, 0, sizeof(git_indexer_stats)); + if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0) + goto on_error; + + *bytes = buffered_size; + + do { + if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 0) + goto on_error; + + gitno_consume_n(&buf, buf.offset); + if ((recvd = gitno_recv(&buf)) < 0) + goto on_error; + + *bytes += recvd; + } while(recvd > 0); + + if (git_indexer_stream_finalize(idx, stats)) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + +on_error: + git_indexer_stream_free(idx); + return -1; +} + +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo) +{ + git_revwalk *walk; + git_strarray refs; + unsigned int i; + git_reference *ref; + + if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + for (i = 0; i < refs.count; ++i) { + /* No tags */ + if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) + continue; + + if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) + goto on_error; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) + continue; + if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) + goto on_error; + + git_reference_free(ref); + } + + git_strarray_free(&refs); + *out = walk; + return 0; + +on_error: + git_reference_free(ref); + git_strarray_free(&refs); + return -1; +} diff --git a/src/fetch.h b/src/fetch.h new file mode 100644 index 000000000..a7f126520 --- /dev/null +++ b/src/fetch.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_fetch_h__ +#define INCLUDE_fetch_h__ + +#include "netops.h" + +int git_fetch_negotiate(git_remote *remote); +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); + +int git_fetch__download_pack(const char *buffered, size_t buffered_size, git_transport *t, + git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); + +#endif diff --git a/src/filebuf.c b/src/filebuf.c index 63f2897cb..876f8e3e7 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 @@ -28,68 +10,117 @@ #include "filebuf.h" #include "fileops.h" +#define GIT_LOCK_FILE_MODE 0644 + static const size_t WRITE_BUFFER_SIZE = (4096 * 2); +enum buferr_t { + BUFERR_OK = 0, + BUFERR_WRITE, + BUFERR_ZLIB, + BUFERR_MEM +}; + +#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } + +static int verify_last_error(git_filebuf *file) +{ + switch (file->last_error) { + case BUFERR_WRITE: + giterr_set(GITERR_OS, "Failed to write out file"); + return -1; + + case BUFERR_MEM: + giterr_set_oom(); + return -1; + + case BUFERR_ZLIB: + giterr_set(GITERR_ZLIB, + "Buffer error when writing out ZLib data"); + return -1; + + default: + return 0; + } +} + static int lock_file(git_filebuf *file, int flags) { - if (gitfo_exists(file->path_lock) == 0) { + if (git_path_exists(file->path_lock) == true) { if (flags & GIT_FILEBUF_FORCE) - gitfo_unlink(file->path_lock); - else - return git__throw(GIT_EOSERR, "Failed to lock file"); + p_unlink(file->path_lock); + else { + giterr_set(GITERR_OS, + "Failed to lock file '%s' for writing", file->path_lock); + return -1; + } } /* create path to the file buffer is required */ if (flags & GIT_FILEBUF_FORCE) { - file->fd = gitfo_creat_force(file->path_lock, 0644); + /* 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); } else { - file->fd = gitfo_creat(file->path_lock, 0644); + file->fd = git_futils_creat_locked(file->path_lock, GIT_LOCK_FILE_MODE); } if (file->fd < 0) - return git__throw(GIT_EOSERR, "Failed to create lock"); + return -1; - /* TODO: do a flock() in the descriptor file_lock */ + file->fd_is_open = true; - if ((flags & GIT_FILEBUF_APPEND) && gitfo_exists(file->path_original) == 0) { + if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) { git_file source; char buffer[2048]; size_t read_bytes; - source = gitfo_open(file->path_original, O_RDONLY); - if (source < 0) - return git__throw(GIT_EOSERR, "Failed to lock file. Could not open %s", file->path_original); + source = p_open(file->path_original, O_RDONLY); + if (source < 0) { + giterr_set(GITERR_OS, + "Failed to open file '%s' for reading", + file->path_original); + return -1; + } - while ((read_bytes = gitfo_read(source, buffer, 2048)) > 0) { - gitfo_write(file->fd, buffer, read_bytes); + while ((read_bytes = p_read(source, buffer, 2048)) > 0) { + p_write(file->fd, buffer, read_bytes); if (file->digest) git_hash_update(file->digest, buffer, read_bytes); } - gitfo_close(source); + p_close(source); } - return GIT_SUCCESS; + return 0; } void git_filebuf_cleanup(git_filebuf *file) { - if (file->fd >= 0) - gitfo_close(file->fd); + if (file->fd_is_open && file->fd >= 0) + p_close(file->fd); - if (file->path_lock && gitfo_exists(file->path_lock) == GIT_SUCCESS) - gitfo_unlink(file->path_lock); + if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock)) + p_unlink(file->path_lock); if (file->digest) git_hash_free_ctx(file->digest); - free(file->buffer); - free(file->z_buf); + if (file->buffer) + git__free(file->buffer); - deflateEnd(&file->zs); + /* use the presence of z_buf to decide if we need to deflateEnd */ + if (file->z_buf) { + git__free(file->z_buf); + deflateEnd(&file->zs); + } - free(file->path_original); - free(file->path_lock); + if (file->path_original) + git__free(file->path_original); + if (file->path_lock) + git__free(file->path_lock); + + memset(file, 0x0, sizeof(git_filebuf)); + file->fd = -1; } GIT_INLINE(int) flush_buffer(git_filebuf *file) @@ -99,43 +130,53 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file) return result; } -static int write_normal(git_filebuf *file, const void *source, size_t len) +int git_filebuf_flush(git_filebuf *file) { - int result = 0; + return flush_buffer(file); +} +static int write_normal(git_filebuf *file, void *source, size_t len) +{ if (len > 0) { - result = gitfo_write(file->fd, (void *)source, len); + if (p_write(file->fd, (void *)source, len) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + if (file->digest) git_hash_update(file->digest, source, len); } - return result; + return 0; } -static int write_deflate(git_filebuf *file, const void *source, size_t len) +static int write_deflate(git_filebuf *file, void *source, size_t len) { - int result = Z_OK; z_stream *zs = &file->zs; if (len > 0 || file->flush_mode == Z_FINISH) { - zs->next_in = (void *)source; - zs->avail_in = len; + zs->next_in = source; + zs->avail_in = (uInt)len; do { - int have; + size_t have; zs->next_out = file->z_buf; - zs->avail_out = file->buf_size; + zs->avail_out = (uInt)file->buf_size; - result = deflate(zs, file->flush_mode); - assert(result != Z_STREAM_ERROR); + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } - have = file->buf_size - zs->avail_out; + have = file->buf_size - (size_t)zs->avail_out; - if (gitfo_write(file->fd, file->z_buf, have) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to write to file"); + if (p_write(file->fd, file->z_buf, have) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } - } while (zs->avail_out == 0); + } while (zs->avail_out == 0); assert(zs->avail_in == 0); @@ -143,52 +184,54 @@ static int write_deflate(git_filebuf *file, const void *source, size_t len) git_hash_update(file->digest, source, len); } - return GIT_SUCCESS; + return 0; } int git_filebuf_open(git_filebuf *file, const char *path, int flags) { - int error; + int compression; size_t path_len; - assert(file && path); + /* opening an already open buffer is a programming error; + * assert that this never happens instead of returning + * an error code */ + assert(file && path && file->buffer == NULL); memset(file, 0x0, sizeof(git_filebuf)); + if (flags & GIT_FILEBUF_DO_NOT_BUFFER) + file->do_not_buffer = true; + file->buf_size = WRITE_BUFFER_SIZE; file->buf_pos = 0; file->fd = -1; + file->last_error = BUFERR_OK; /* Allocate the main cache buffer */ - file->buffer = git__malloc(file->buf_size); - if (file->buffer == NULL){ - error = GIT_ENOMEM; - goto cleanup; + if (!file->do_not_buffer) { + file->buffer = git__malloc(file->buf_size); + GITERR_CHECK_ALLOC(file->buffer); } /* If we are hashing on-write, allocate a new hash context */ if (flags & GIT_FILEBUF_HASH_CONTENTS) { - if ((file->digest = git_hash_new_ctx()) == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + file->digest = git_hash_new_ctx(); + GITERR_CHECK_ALLOC(file->digest); } - /* If we are deflating on-write, */ - if (flags & GIT_FILEBUF_DEFLATE_CONTENTS) { + compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; + /* If we are deflating on-write, */ + if (compression != 0) { /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, Z_BEST_SPEED) != Z_OK) { - error = git__throw(GIT_EZLIB, "Failed to initialize zlib"); + if (deflateInit(&file->zs, compression) != Z_OK) { + giterr_set(GITERR_ZLIB, "Failed to initialize zlib"); goto cleanup; } /* Allocate the Zlib cache buffer */ file->z_buf = git__malloc(file->buf_size); - if (file->z_buf == NULL){ - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->z_buf); /* Never flush */ file->flush_mode = Z_NO_FLUSH; @@ -199,102 +242,105 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) /* If we are writing to a temp file */ if (flags & GIT_FILEBUF_TEMPORARY) { - char tmp_path[GIT_PATH_MAX]; + git_buf tmp_path = GIT_BUF_INIT; /* Open the file as temporary for locking */ - file->fd = gitfo_mktemp(tmp_path, path); + file->fd = git_futils_mktmp(&tmp_path, path); + if (file->fd < 0) { - error = GIT_EOSERR; + git_buf_free(&tmp_path); goto cleanup; } + file->fd_is_open = true; /* No original path */ file->path_original = NULL; - file->path_lock = git__strdup(tmp_path); - - if (file->path_lock == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + file->path_lock = git_buf_detach(&tmp_path); + GITERR_CHECK_ALLOC(file->path_lock); } else { path_len = strlen(path); /* Save the original path of the file */ file->path_original = git__strdup(path); - if (file->path_original == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->path_original); /* create the locking path by appending ".lock" to the original */ file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH); - if (file->path_lock == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->path_lock); memcpy(file->path_lock, file->path_original, path_len); memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); /* open the file for locking */ - if ((error = lock_file(file, flags)) < GIT_SUCCESS) + if (lock_file(file, flags) < 0) goto cleanup; } - return GIT_SUCCESS; + return 0; cleanup: git_filebuf_cleanup(file); - return git__rethrow(error, "Failed to open file buffer for '%s'", path); + return -1; } int git_filebuf_hash(git_oid *oid, git_filebuf *file) { - int error; - assert(oid && file && file->digest); - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to get hash for file"); + flush_buffer(file); + + if (verify_last_error(file) < 0) + return -1; git_hash_final(oid, file->digest); git_hash_free_ctx(file->digest); file->digest = NULL; - return GIT_SUCCESS; + return 0; } -int git_filebuf_commit_at(git_filebuf *file, const char *path) +int git_filebuf_commit_at(git_filebuf *file, const char *path, mode_t mode) { - free(file->path_original); + git__free(file->path_original); file->path_original = git__strdup(path); - if (file->path_original == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(file->path_original); - return git_filebuf_commit(file); + return git_filebuf_commit(file, mode); } -int git_filebuf_commit(git_filebuf *file) +int git_filebuf_commit(git_filebuf *file, mode_t mode) { - int error; - /* temporary files cannot be committed */ assert(file && file->path_original); file->flush_mode = Z_FINISH; - if ((error = flush_buffer(file)) < GIT_SUCCESS) - goto cleanup; + flush_buffer(file); - gitfo_close(file->fd); + if (verify_last_error(file) < 0) + goto on_error; + + p_close(file->fd); file->fd = -1; + file->fd_is_open = false; - error = gitfo_mv(file->path_lock, file->path_original); + 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) { + giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original); + goto on_error; + } -cleanup: git_filebuf_cleanup(file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to commit locked file from buffer"); - return GIT_SUCCESS; + return 0; + +on_error: + git_filebuf_cleanup(file); + return -1; } GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) @@ -305,80 +351,107 @@ GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) { - int error; const unsigned char *buf = buff; + ENSURE_BUF_OK(file); + + if (file->do_not_buffer) + return file->write(file, (void *)buff, len); + for (;;) { size_t space_left = file->buf_size - file->buf_pos; /* cache if it's small */ if (space_left > len) { add_to_cache(file, buf, len); - return GIT_SUCCESS; + return 0; } - /* flush the cache if it doesn't fit */ - if (file->buf_pos > 0) { - add_to_cache(file, buf, space_left); + add_to_cache(file, buf, space_left); + if (flush_buffer(file) < 0) + return -1; - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write to buffer"); - - len -= space_left; - buf += space_left; - } - - /* write too-large chunks immediately */ - if (len > file->buf_size) { - error = file->write(file, buf, len); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write to buffer"); - } + len -= space_left; + buf += space_left; } } int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) { - int error; size_t space_left = file->buf_size - file->buf_pos; *buffer = NULL; - if (len > file->buf_size) - return GIT_ENOMEM; + ENSURE_BUF_OK(file); + + if (len > file->buf_size) { + file->last_error = BUFERR_MEM; + return -1; + } if (space_left <= len) { - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to reserve buffer"); + if (flush_buffer(file) < 0) + return -1; } *buffer = (file->buffer + file->buf_pos); file->buf_pos += len; - return GIT_SUCCESS; + return 0; } int git_filebuf_printf(git_filebuf *file, const char *format, ...) { va_list arglist; - size_t space_left = file->buf_size - file->buf_pos; - int len, error; + size_t space_left; + int len, res; + char *tmp_buffer; - va_start(arglist, format); + ENSURE_BUF_OK(file); - len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + space_left = file->buf_size - file->buf_pos; - if (len < 0 || (size_t)len >= space_left) { - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to output to buffer"); + do { + va_start(arglist, format); + len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + va_end(arglist); - len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); - if (len < 0 || (size_t)len > file->buf_size) - return GIT_ENOMEM; + if (len < 0) { + file->last_error = BUFERR_MEM; + return -1; + } + + if ((size_t)len + 1 <= space_left) { + file->buf_pos += len; + return 0; + } + + if (flush_buffer(file) < 0) + return -1; + + space_left = file->buf_size - file->buf_pos; + + } while ((size_t)len + 1 <= space_left); + + tmp_buffer = git__malloc(len + 1); + if (!tmp_buffer) { + file->last_error = BUFERR_MEM; + return -1; } - file->buf_pos += len; - return GIT_SUCCESS; + va_start(arglist, format); + len = p_vsnprintf(tmp_buffer, len + 1, format, arglist); + va_end(arglist); + if (len < 0) { + git__free(tmp_buffer); + file->last_error = BUFERR_MEM; + return -1; + } + + res = git_filebuf_write(file, tmp_buffer, len); + git__free(tmp_buffer); + + return res; } diff --git a/src/filebuf.h b/src/filebuf.h index 37cb36784..377883147 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -1,9 +1,15 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_filebuf_h__ #define INCLUDE_filebuf_h__ #include "fileops.h" #include "hash.h" -#include "git2/zlib.h" +#include #ifdef GIT_THREADS # define GIT_FILEBUF_THREADS @@ -13,7 +19,8 @@ #define GIT_FILEBUF_APPEND (1 << 2) #define GIT_FILEBUF_FORCE (1 << 3) #define GIT_FILEBUF_TEMPORARY (1 << 4) -#define GIT_FILEBUF_DEFLATE_CONTENTS (1 << 5) +#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) +#define GIT_FILEBUF_DEFLATE_SHIFT (6) #define GIT_FILELOCK_EXTENSION ".lock\0" #define GIT_FILELOCK_EXTLENGTH 6 @@ -22,8 +29,7 @@ struct git_filebuf { char *path_original; char *path_lock; - int (*write)(struct git_filebuf *file, - const void *source, size_t len); + int (*write)(struct git_filebuf *file, void *source, size_t len); git_hash_ctx *digest; @@ -35,18 +41,46 @@ struct git_filebuf { size_t buf_size, buf_pos; git_file fd; + bool fd_is_open; + bool do_not_buffer; + int last_error; }; typedef struct git_filebuf git_filebuf; +#define GIT_FILEBUF_INIT {0} + +/* + * The git_filebuf object lifecycle is: + * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. + * + * - Call git_filebuf_open() to initialize the filebuf for use. + * + * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), + * git_filebuf_reserve() as you like. The error codes for these + * functions don't need to be checked. They are stored internally + * by the file buffer. + * + * - While you are writing, you may call git_filebuf_hash() to get + * the hash of all you have written so far. This function will + * fail if any of the previous writes to the buffer failed. + * + * - To close the git_filebuf, you may call git_filebuf_commit() or + * git_filebuf_commit_at() to save the file, or + * git_filebuf_cleanup() to abandon the file. All of these will + * free the git_filebuf object. Likewise, all of these will fail + * if any of the previous writes to the buffer failed, and set + * an error code accordingly. + */ 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, ...); +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); -int git_filebuf_commit_at(git_filebuf *lock, const char *path); +int git_filebuf_commit(git_filebuf *lock, mode_t mode); +int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); #endif diff --git a/src/fileops.c b/src/fileops.c index 73939349d..95a65893c 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -1,350 +1,283 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ #include "common.h" #include "fileops.h" #include -int gitfo_mkdir_2file(const char *file_path) +int git_futils_mkpath2file(const char *file_path, const mode_t mode) { - const int mode = 0755; /* or 0777 ? */ - int error = GIT_SUCCESS; - char target_folder_path[GIT_PATH_MAX]; + int result = 0; + git_buf target_folder = GIT_BUF_INIT; - error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path); - if (error < GIT_SUCCESS) - return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); + if (git_path_dirname_r(&target_folder, file_path) < 0) + return -1; /* Does the containing folder exist? */ - if (gitfo_isdir(target_folder_path)) { - git__joinpath(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */ - + if (git_path_isdir(target_folder.ptr) == false) /* Let's create the tree structure */ - error = gitfo_mkdir_recurs(target_folder_path, mode); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ - } + result = git_futils_mkdir_r(target_folder.ptr, NULL, mode); - return GIT_SUCCESS; + git_buf_free(&target_folder); + return result; } -int gitfo_mktemp(char *path_out, const char *filename) +int git_futils_mktmp(git_buf *path_out, const char *filename) { int fd; - strcpy(path_out, filename); - strcat(path_out, "_git2_XXXXXX"); + git_buf_sets(path_out, filename); + git_buf_puts(path_out, "_git2_XXXXXX"); -#if defined(_MSC_VER) - /* FIXME: there may be race conditions when multi-threading - * with the library */ - if (_mktemp_s(path_out, GIT_PATH_MAX) != 0) - return git__throw(GIT_EOSERR, "Failed to make temporary file %s", path_out); + if (git_buf_oom(path_out)) + return -1; - fd = gitfo_creat(path_out, 0744); + if ((fd = p_mkstemp(path_out->ptr)) < 0) { + giterr_set(GITERR_OS, + "Failed to create temporary file '%s'", path_out->ptr); + return -1; + } + + return fd; +} + +int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + int fd; + + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + fd = p_creat(path, mode); + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create file '%s'", path); + return -1; + } + + return fd; +} + +int git_futils_creat_locked(const char *path, const mode_t mode) +{ + int fd; + +#ifdef GIT_WIN32 + wchar_t* buf; + + buf = gitwin_to_utf16(path); + fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); + git__free(buf); #else - fd = mkstemp(path_out); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); #endif - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out); -} - -int gitfo_open(const char *path, int flags) -{ - int fd = open(path, flags | O_BINARY); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to open %s", path); -} - -int gitfo_creat(const char *path, int mode) -{ - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create file. Could not open %s", path); -} - -int gitfo_creat_force(const char *path, int mode) -{ - if (gitfo_mkdir_2file(path) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to create file %s", path); - - return gitfo_creat(path, mode); -} - -int gitfo_read(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - while (cnt) { - ssize_t r = read(fd, b, cnt); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return git__throw(GIT_EOSERR, "Failed to read from file"); - } - if (!r) { - errno = EPIPE; - return git__throw(GIT_EOSERR, "Failed to read from file"); - } - cnt -= r; - b += r; - } - return GIT_SUCCESS; -} - -int gitfo_write(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - while (cnt) { - ssize_t r = write(fd, b, cnt); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return git__throw(GIT_EOSERR, "Failed to write to file"); - } - if (!r) { - errno = EPIPE; - return git__throw(GIT_EOSERR, "Failed to write to file"); - } - cnt -= r; - b += r; - } - return GIT_SUCCESS; -} - -int gitfo_isdir(const char *path) -{ - struct stat st; - int len, stat_error; - - if (!path) - return git__throw(GIT_ENOTFOUND, "No path given to gitfo_isdir"); - - len = strlen(path); - - /* win32: stat path for folders cannot end in a slash */ - if (path[len - 1] == '/') { - char *path_fixed = NULL; - path_fixed = git__strdup(path); - path_fixed[len - 1] = 0; - stat_error = gitfo_stat(path_fixed, &st); - free(path_fixed); - } else { - stat_error = gitfo_stat(path, &st); + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); + return -1; } - if (stat_error < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "%s does not exist", path); - - if (!S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "%s is a file", path); - - return GIT_SUCCESS; + return fd; } -int gitfo_exists(const char *path) +int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) { - assert(path); - return access(path, F_OK); + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + return git_futils_creat_locked(path, mode); } -git_off_t gitfo_size(git_file fd) +int git_futils_open_ro(const char *path) +{ + int fd = p_open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + fd = GIT_ENOTFOUND; + giterr_set(GITERR_OS, "Failed to open '%s'", path); + } + return fd; +} + +git_off_t git_futils_filesize(git_file fd) { struct stat sb; - if (gitfo_fstat(fd, &sb)) - return git__throw(GIT_EOSERR, "Failed to get size of file. File missing or corrupted"); + + if (p_fstat(fd, &sb)) { + giterr_set(GITERR_OS, "Failed to stat file descriptor"); + return -1; + } + return sb.st_size; } -int gitfo_read_file(gitfo_buf *obj, const char *path) +mode_t git_futils_canonical_mode(mode_t raw_mode) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else + return 0; +} + +int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) { git_file fd; size_t len; - git_off_t size; - unsigned char *buff; + struct stat st; - assert(obj && path && *path); + assert(buf && path && *path); - if ((fd = gitfo_open(path, O_RDONLY)) < 0) - return git__throw(GIT_ERROR, "Failed to open %s for reading", path); + if (updated != NULL) + *updated = 0; - if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) { - gitfo_close(fd); - return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path); - } - len = (size_t) size; + if ((fd = git_futils_open_ro(path)) < 0) + return fd; - if ((buff = git__malloc(len + 1)) == NULL) { - gitfo_close(fd); - return GIT_ENOMEM; + if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { + p_close(fd); + giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); + return -1; } - if (gitfo_read(fd, buff, len) < 0) { - gitfo_close(fd); - free(buff); - return git__throw(GIT_ERROR, "Failed to read file `%s`", path); - } - buff[len] = '\0'; - - gitfo_close(fd); - - obj->data = buff; - obj->len = len; - - return GIT_SUCCESS; -} - -void gitfo_free_buf(gitfo_buf *obj) -{ - assert(obj); - free(obj->data); - obj->data = NULL; -} - -int gitfo_mv(const char *from, const char *to) -{ - int error; - -#ifdef GIT_WIN32 /* - * Win32 POSIX compilance my ass. If the destination - * file exists, the `rename` call fails. This is as - * close as it gets with the Win32 API. + * If we were given a time, we only want to read the file if it + * has been modified. */ - error = MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; -#else - /* Don't even try this on Win32 */ - if (!link(from, to)) { - gitfo_unlink(from); - return GIT_SUCCESS; + if (mtime != NULL && *mtime >= st.st_mtime) { + p_close(fd); + return 0; } - if (!rename(from, to)) - return GIT_SUCCESS; + if (mtime != NULL) + *mtime = st.st_mtime; - error = GIT_EOSERR; -#endif + len = (size_t) st.st_size; - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to move file from `%s` to `%s`", from, to); + git_buf_clear(buf); - return GIT_SUCCESS; -} - -int gitfo_mv_force(const char *from, const char *to) -{ - if (gitfo_mkdir_2file(to) < GIT_SUCCESS) - return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ - - return gitfo_mv(from, to); /* The callee already takes care of setting the correct error message. */ -} - -int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len) -{ - if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS) - return GIT_EOSERR; - return GIT_SUCCESS; -} - -void gitfo_free_map(git_map *out) -{ - git__munmap(out); -} - -int gitfo_dirent( - char *path, - size_t path_sz, - int (*fn)(void *, char *), - void *arg) -{ - size_t wd_len = strlen(path); - DIR *dir; - struct dirent *de; - - if (!wd_len || path_sz < wd_len + 2) - return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path); - - while (path[wd_len - 1] == '/') - wd_len--; - path[wd_len++] = '/'; - path[wd_len] = '\0'; - - dir = opendir(path); - if (!dir) - return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path); - - while ((de = readdir(dir)) != NULL) { - size_t de_len; - int result; - - /* always skip '.' and '..' */ - if (de->d_name[0] == '.') { - if (de->d_name[1] == '\0') - continue; - if (de->d_name[1] == '.' && de->d_name[2] == '\0') - continue; - } - - de_len = strlen(de->d_name); - if (path_sz < wd_len + de_len + 1) { - closedir(dir); - return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path); - } - - strcpy(path + wd_len, de->d_name); - result = fn(arg, path); - if (result < GIT_SUCCESS) { - closedir(dir); - return result; /* The callee is reponsible for setting the correct error message */ - } - if (result > 0) { - closedir(dir); - return result; - } + if (git_buf_grow(buf, len + 1) < 0) { + p_close(fd); + return -1; } - closedir(dir); - return GIT_SUCCESS; + buf->ptr[len] = '\0'; + + while (len > 0) { + ssize_t read_size = p_read(fd, buf->ptr, len); + + if (read_size < 0) { + p_close(fd); + giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path); + return -1; + } + + len -= read_size; + buf->size += read_size; + } + + p_close(fd); + + if (updated != NULL) + *updated = 1; + + return 0; } - -int retrieve_path_root_offset(const char *path) +int git_futils_readbuffer(git_buf *buf, const char *path) { - int offset = 0; - -#ifdef GIT_WIN32 - - /* Does the root of the path look like a windows drive ? */ - if (isalpha(path[0]) && (path[1] == ':')) - offset += 2; - -#endif - - if (*(path + offset) == '/') - return offset; - - return -1; /* Not a real error. Rather a signal than the path is not rooted */ + return git_futils_readbuffer_updated(buf, path, NULL, NULL); } - -int gitfo_mkdir_recurs(const char *path, int mode) +int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) { - int error, root_path_offset; + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; + + if (p_rename(from, to) < 0) { + giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to); + return -1; + } + + return 0; +} + +int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) +{ + return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); +} + +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = git_futils_open_ro(path); + git_off_t len; + int result; + + if (fd < 0) + return fd; + + len = git_futils_filesize(fd); + if (!git__is_sizet(len)) { + giterr_set(GITERR_OS, "File `%s` too large to mmap", path); + return -1; + } + + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); + p_close(fd); + return result; +} + +void git_futils_mmap_free(git_map *out) +{ + p_munmap(out); +} + +int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) +{ + git_buf make_path = GIT_BUF_INIT; + size_t start = 0; char *pp, *sp; - char *path_copy = git__strdup(path); + bool failed = false; - if (path_copy == NULL) - return GIT_ENOMEM; + if (base != NULL) { + /* + * when a base is being provided, it is supposed to already exist. + * Therefore, no attempt is being made to recursively create this leading path + * segment. It's just skipped. */ + start = strlen(base); + if (git_buf_joinpath(&make_path, base, path) < 0) + return -1; + } else { + int root_path_offset; - error = GIT_SUCCESS; - pp = path_copy; + if (git_buf_puts(&make_path, path) < 0) + return -1; - root_path_offset = retrieve_path_root_offset(pp); - if (root_path_offset > 0) - pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ + root_path_offset = git_path_root(make_path.ptr); + if (root_path_offset > 0) { + /* + * On Windows, will skip the drive name (eg. C: or D:) + * or the leading part of a network path (eg. //computer_name ) */ + start = root_path_offset; + } + } - while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) { - if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) { + pp = make_path.ptr + start; + + while (!failed && (sp = strchr(pp, '/')) != NULL) { + if (sp != pp && git_path_isdir(make_path.ptr) == false) { *sp = 0; - error = gitfo_mkdir(path_copy, mode); /* Do not choke while trying to recreate an existing directory */ - if (errno == EEXIST) - error = GIT_SUCCESS; + if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) + failed = true; *sp = '/'; } @@ -352,201 +285,198 @@ int gitfo_mkdir_recurs(const char *path, int mode) pp = sp + 1; } - if (*(pp - 1) != '/' && error == GIT_SUCCESS) - error = gitfo_mkdir(path, mode); - - free(path_copy); - - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to recursively create `%s` tree structure", path); - - return GIT_SUCCESS; -} - -static int retrieve_previous_path_component_start(const char *path) -{ - int offset, len, root_offset, start = 0; - - root_offset = retrieve_path_root_offset(path); - if (root_offset > -1) - start += root_offset; - - len = strlen(path); - offset = len - 1; - - /* Skip leading slash */ - if (path[start] == '/') - start++; - - /* Skip trailing slash */ - if (path[offset] == '/') - offset--; - - if (offset < root_offset) - return git__throw(GIT_ERROR, "Failed to retrieve path component. Wrong offset"); - - while (offset > start && path[offset-1] != '/') { - offset--; + if (*pp != '\0' && !failed) { + if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) + failed = true; } - return offset; -} + git_buf_free(&make_path); -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) -{ - int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS; - char *current; - const char *buffer_out_start, *buffer_end; - - current = (char *)path; - buffer_end = path + strlen(path); - buffer_out_start = buffer_out; - - root_path_offset = retrieve_path_root_offset(path); - if (root_path_offset < 0) { - error = gitfo_getcwd(buffer_out, size); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ - - len = strlen(buffer_out); - buffer_out += len; + if (failed) { + giterr_set(GITERR_OS, + "Failed to create directory structure at '%s'", path); + return -1; } - while (current < buffer_end) { - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') { - current++; - continue; - } - - only_dots = 1; - segment_len = 0; - - /* Copy path segment to the output */ - while (current < buffer_end && *current != '/') - { - only_dots &= (*current == '.'); - *buffer_out++ = *current++; - segment_len++; - len++; - } - - /* Skip current directory */ - if (only_dots && segment_len == 1) - { - current++; - buffer_out -= segment_len; - len -= segment_len; - continue; - } - - /* Handle the double-dot upward directory navigation */ - if (only_dots && segment_len == 2) - { - current++; - buffer_out -= segment_len; - - *buffer_out ='\0'; - len = retrieve_previous_path_component_start(buffer_out_start); - - /* Are we escaping out of the root dir? */ - if (len < 0) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path); - - buffer_out = (char *)buffer_out_start + len; - continue; - } - - /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */ - if (only_dots && segment_len > 0) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path); - - *buffer_out++ = '/'; - len++; - } - - *buffer_out = '\0'; - - return GIT_SUCCESS; -} - -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) -{ - int error, path_len, i; - const char* pattern = "/.."; - - path_len = strlen(path); - - /* Let's make sure the filename isn't empty nor a dot */ - if (path_len == 0 || (path_len == 1 && *path == '.')) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", path); - - /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ - for (i = 1; path_len > i && i < 4; i++) { - if (!strncmp(path + path_len - i, pattern, i)) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); - } - - error = gitfo_prettify_dir_path(buffer_out, size, path); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ - - path_len = strlen(buffer_out); - if (path_len < 2) /* TODO: Fixme. We should also take of detecting Windows rooted path (probably through usage of retrieve_path_root_offset) */ - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); - - /* Remove the trailing slash */ - buffer_out[path_len - 1] = '\0'; - - return GIT_SUCCESS; -} - -int gitfo_cmp_path(const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2) -{ - int len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = memcmp(name1, name2, len); - if (cmp) - return cmp; - if (len1 < len2) - return ((!isdir1 && !isdir2) ? -1 : - (isdir1 ? '/' - name2[len1] : name2[len1] - '/')); - if (len1 > len2) - return ((!isdir1 && !isdir2) ? 1 : - (isdir2 ? name1[len2] - '/' : '/' - name1[len2])); return 0; } -static void posixify_path(char *path) +static int _rmdir_recurs_foreach(void *opaque, git_buf *path) { - while (*path) { - if (*path == '\\') - *path = '/'; + git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque; - path++; + assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY + || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS + || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS); + + if (git_path_isdir(path->ptr) == true) { + if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0) + return -1; + + if (p_rmdir(path->ptr) < 0) { + if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST)) + return 0; + + giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr); + return -1; + } + + return 0; } + + if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) { + if (p_unlink(path->ptr) < 0) { + giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr); + return -1; + } + + return 0; + } + + if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) { + giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr); + return -1; + } + + return 0; } -int gitfo_getcwd(char *buffer_out, size_t size) +int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type) { - char *cwd_buffer; + int error; + git_buf p = GIT_BUF_INIT; - assert(buffer_out && size > 0); + error = git_buf_sets(&p, path); + if (!error) + error = _rmdir_recurs_foreach(&removal_type, &p); + git_buf_free(&p); + return error; +} #ifdef GIT_WIN32 - cwd_buffer = _getcwd(buffer_out, size); -#else - cwd_buffer = getcwd(buffer_out, size); +struct win32_path { + wchar_t path[MAX_PATH]; + DWORD len; +}; + +static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) +{ + s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); + return s_root->len ? 0 : -1; +} + +static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) +{ + int error = 0; + size_t len; + wchar_t *file_utf16 = NULL; + char *file_utf8 = NULL; + + if (!root || !filename || (len = strlen(filename)) == 0) + return GIT_ENOTFOUND; + + /* allocate space for wchar_t path to file */ + file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); + GITERR_CHECK_ALLOC(file_utf16); + + /* append root + '\\' + filename as wchar_t */ + memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); + + if (*filename == '/' || *filename == '\\') + filename++; + + if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != + (int)len + 1) { + error = -1; + goto cleanup; + } + + /* check access */ + if (_waccess(file_utf16, F_OK) < 0) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + /* convert to utf8 */ + if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) + error = -1; + else { + git_path_mkposix(file_utf8); + git_buf_attach(path, file_utf8, 0); + } + +cleanup: + git__free(file_utf16); + return error; +} #endif - if (cwd_buffer == NULL) - return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); +int git_futils_find_system_file(git_buf *path, const char *filename) +{ +#ifdef GIT_WIN32 + struct win32_path root; - posixify_path(buffer_out); + if (win32_expand_path(&root, L"%PROGRAMFILES%\\Git\\etc\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the system's Program Files directory"); + return -1; + } - git__joinpath(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash + if (win32_find_file(path, &root, filename) < 0) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } - return GIT_SUCCESS; + return 0; + +#else + if (git_buf_joinpath(path, "/etc", filename) < 0) + return -1; + + if (git_path_exists(path->ptr) == true) + return 0; + + git_buf_clear(path); + return GIT_ENOTFOUND; +#endif +} + +int git_futils_find_global_file(git_buf *path, const char *filename) +{ +#ifdef GIT_WIN32 + struct win32_path root; + + if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the user's profile directory"); + return -1; + } + + if (win32_find_file(path, &root, filename) < 0) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } + + return 0; +#else + const char *home = getenv("HOME"); + + if (home == NULL) { + giterr_set(GITERR_OS, "Global file lookup failed. " + "Cannot locate the user's home directory"); + return -1; + } + + if (git_buf_joinpath(path, home, filename) < 0) + return -1; + + if (git_path_exists(path->ptr) == false) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } + + return 0; +#endif } diff --git a/src/fileops.h b/src/fileops.h index d0381b675..b0c5779e5 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -1,94 +1,118 @@ /* - * fileops.h - OS agnostic disk io operations + * Copyright (C) 2009-2012 the libgit2 contributors * - * This header describes the strictly internal part of the api + * 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_fileops_h__ #define INCLUDE_fileops_h__ #include "common.h" #include "map.h" -#include "dir.h" -#include -#include +#include "posix.h" +#include "path.h" -#ifdef GIT_WIN32 -GIT_INLINE(int) link(const char *GIT_UNUSED(old), const char *GIT_UNUSED(new)) -{ - GIT_UNUSED_ARG(old) - GIT_UNUSED_ARG(new) - errno = ENOSYS; - return -1; -} +/** + * Filebuffer methods + * + * Read whole files into an in-memory buffer for processing + */ +extern int git_futils_readbuffer(git_buf *obj, const char *path); +extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); -GIT_INLINE(int) git__mkdir(const char *path, int GIT_UNUSED(mode)) -{ - GIT_UNUSED_ARG(mode) - return mkdir(path); -} +/** + * File utils + * + * These are custom filesystem-related helper methods. They are + * rather high level, and wrap the underlying POSIX methods + * + * All these methods return 0 on success, + * or an error code on failure and an error message is set. + */ -extern int git__unlink(const char *path); -extern int git__mkstemp(char *template); -extern int git__fsync(int fd); +/** + * Create and open a file, while also + * creating all the folders in its path + */ +extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); -# ifndef GIT__WIN32_NO_HIDE_FILEOPS -# define unlink(p) git__unlink(p) -# define mkstemp(t) git__mkstemp(t) -# define mkdir(p,m) git__mkdir(p, m) -# define fsync(fd) git__fsync(fd) -# endif -#endif /* GIT_WIN32 */ +/** + * Create an open a process-locked file + */ +extern int git_futils_creat_locked(const char *path, const mode_t mode); +/** + * Create an open a process-locked file, while + * also creating all the folders in its path + */ +extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); -#if !defined(O_BINARY) -#define O_BINARY 0 -#endif +/** + * Create a path recursively + * + * If a base parameter is being passed, it's expected to be valued with a path pointing to an already + * exisiting directory. + */ +extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); -#define GITFO_BUF_INIT {NULL, 0} +/** + * Create all the folders required to contain + * the full path of a file + */ +extern int git_futils_mkpath2file(const char *path, const mode_t mode); -typedef int git_file; +typedef enum { + GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0, + GIT_DIRREMOVAL_FILES_AND_DIRS = 1, + GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2, +} git_directory_removal_type; -typedef struct { /* file io buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -} gitfo_buf; +/** + * Remove path and any files and directories beneath it. + * + * @param path Path to to top level directory to process. + * + * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy + * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS + * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove + * empty directories (no failure on file encounter). + * + * @return 0 on success; -1 on error. + */ +extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type); -extern int gitfo_exists(const char *path); -extern int gitfo_open(const char *path, int flags); -extern int gitfo_creat(const char *path, int mode); -extern int gitfo_creat_force(const char *path, int mode); -extern int gitfo_mktemp(char *path_out, const char *filename); -extern int gitfo_isdir(const char *path); -extern int gitfo_mkdir_recurs(const char *path, int mode); -extern int gitfo_mkdir_2file(const char *path); -#define gitfo_close(fd) close(fd) +/** + * 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 gitfo_read(git_file fd, void *buf, size_t cnt); -extern int gitfo_write(git_file fd, void *buf, size_t cnt); -#define gitfo_lseek(f,n,w) lseek(f, n, w) -extern git_off_t gitfo_size(git_file fd); +/** + * Move a file on the filesystem, create the + * destination path if it doesn't exist + */ +extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); -extern int gitfo_read_file(gitfo_buf *obj, const char *path); -extern void gitfo_free_buf(gitfo_buf *obj); +/** + * Open a file readonly and set error if needed. + */ +extern int git_futils_open_ro(const char *path); -/* Move (rename) a file; this operation is atomic */ -extern int gitfo_mv(const char *from, const char *to); +/** + * Get the filesize in bytes of a file + */ +extern git_off_t git_futils_filesize(git_file fd); -/* Move a file (forced); this will create the destination - * path if it doesn't exist */ -extern int gitfo_mv_force(const char *from, const char *to); +#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 gitfo_stat(p,b) stat(p, b) -#define gitfo_fstat(f,b) fstat(f, b) +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); -#define gitfo_unlink(p) unlink(p) -#define gitfo_rmdir(p) rmdir(p) -#define gitfo_chdir(p) chdir(p) -#define gitfo_mkdir(p,m) mkdir(p, m) - -#define gitfo_mkstemp(t) mkstemp(t) -#define gitfo_fsync(fd) fsync(fd) -#define gitfo_chmod(p,m) chmod(p, m) /** * Read-only map all or part of a file into memory. @@ -102,90 +126,57 @@ extern int gitfo_mv_force(const char *from, const char *to); * @param begin first byte to map, this should be page aligned. * @param end number of bytes to map. * @return - * - GIT_SUCCESS on success; - * - GIT_EOSERR on an unspecified OS related error. + * - 0 on success; + * - -1 on error. */ -extern int gitfo_map_ro( +extern int git_futils_mmap_ro( git_map *out, git_file fd, git_off_t begin, size_t len); +/** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - 0 on success; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. + */ +extern int git_futils_mmap_ro_file( + git_map *out, + const char *path); + /** * Release the memory associated with a previous memory mapping. * @param map the mapping description previously configured. */ -extern void gitfo_free_map(git_map *map); +extern void git_futils_mmap_free(git_map *map); /** - * Walk each directory entry, except '.' and '..', calling fn(state). + * Find a "global" file (i.e. one in a user's home directory). * - * @param pathbuf buffer the function reads the initial directory - * path from, and updates with each successive entry's name. - * @param pathmax maximum allocation of pathbuf. - * @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. - */ -extern int gitfo_dirent( - char *pathbuf, - size_t pathmax, - int (*fn)(void *, char *), - void *state); - -extern int gitfo_cmp_path(const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2); - -extern int gitfo_getcwd(char *buffer_out, size_t size); - -/** - * Clean up a provided absolute or relative directory path. - * - * This prettification relies on basic operations such as coalescing - * multiple forward slashes into a single slash, removing '.' and - * './' current directory segments, and removing parent directory - * whenever '..' is encountered. - * - * If not empty, the returned path ends with a forward slash. - * - * For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3/". - * - * This only performs a string based analysis of the path. - * No checks are done to make sure the path actually makes sense from - * the file system perspective. - * - * @param buffer_out buffer to populate with the normalized path. - * @param size buffer size. - * @param path directory path to clean up. + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS on success; - * - GIT_ERROR when the input path is invalid or escapes the current directory. + * - 0 if found; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. */ -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path); +extern int git_futils_find_global_file(git_buf *path, const char *filename); /** - * Clean up a provided absolute or relative file path. - * - * This prettification relies on basic operations such as coalescing - * multiple forward slashes into a single slash, removing '.' and - * './' current directory segments, and removing parent directory - * whenever '..' is encountered. + * Find a "system" file (i.e. one shared for all users of the system). * - * For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3". - * - * This only performs a string based analysis of the path. - * No checks are done to make sure the path actually makes sense from - * the file system perspective. - * - * @param buffer_out buffer to populate with the normalized path. - * @param size buffer size. - * @param path file path to clean up. + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS on success; - * - GIT_ERROR when the input path is invalid or escapes the current directory. + * - 0 if found; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. */ -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path); +extern int git_futils_find_system_file(git_buf *path, const char *filename); -int retrieve_path_root_offset(const char *path); #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 000000000..8fa3eb684 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" +#include "git2/config.h" + +/* Tweaked from Core Git. I wonder what we could use this for... */ +void git_text_gather_stats(git_text_stats *stats, const git_buf *text) +{ + size_t i; + + memset(stats, 0, sizeof(*stats)); + + for (i = 0; i < git_buf_len(text); i++) { + unsigned char c = text->ptr[i]; + + if (c == '\r') { + stats->cr++; + + if (i + 1 < git_buf_len(text) && text->ptr[i + 1] == '\n') + stats->crlf++; + } + + else if (c == '\n') + stats->lf++; + + else if (c == 0x85) + /* Unicode CR+LF */ + stats->crlf++; + + else if (c == 127) + /* DEL */ + stats->nonprintable++; + + else if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) { + switch (c) { + /* BS, HT, ESC and FF */ + case '\b': case '\t': case '\033': case '\014': + stats->printable++; + break; + case 0: + stats->nul++; + /* fall through */ + default: + stats->nonprintable++; + } + } + + else + stats->printable++; + } + + /* If file ends with EOF then don't count this EOF as non-printable. */ + if (git_buf_len(text) >= 1 && text->ptr[text->size - 1] == '\032') + stats->nonprintable--; +} + +/* + * Fresh from Core Git + */ +int git_text_is_binary(git_text_stats *stats) +{ + if (stats->nul) + return 1; + + if ((stats->printable >> 7) < stats->nonprintable) + return 1; + /* + * Other heuristics? Average line length might be relevant, + * as might LF vs CR vs CRLF counts.. + * + * NOTE! It might be normal to have a low ratio of CRLF to LF + * (somebody starts with a LF-only file and edits it with an editor + * that adds CRLF only to lines that are added..). But do we + * want to support CR-only? Probably not. + */ + return 0; +} + +int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) +{ + 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 { + giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet"); + return -1; + } + + return (int)filters->length; +} + +void git_filters_free(git_vector *filters) +{ + size_t i; + git_filter *filter; + + git_vector_foreach(filters, i, filter) { + if (filter->do_free != NULL) + filter->do_free(filter); + else + git__free(filter); + } + + git_vector_free(filters); +} + +int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) +{ + unsigned int i, src; + git_buf *dbuffer[2]; + + dbuffer[0] = source; + dbuffer[1] = dest; + + src = 0; + + if (git_buf_len(source) == 0) { + git_buf_clear(dest); + 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) + return -1; + + for (i = 0; i < filters->length; ++i) { + git_filter *filter = git_vector_get(filters, i); + unsigned int dst = 1 - src; + + git_buf_clear(dbuffer[dst]); + + /* Apply the filter from dbuffer[src] to the other buffer; + * if the filtering is canceled by the user mid-filter, + * we skip to the next filter without changing the source + * 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; + } + + /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */ + if (src != 1) + git_buf_swap(dest, source); + + return 0; +} + diff --git a/src/filter.h b/src/filter.h new file mode 100644 index 000000000..66e370aef --- /dev/null +++ b/src/filter.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_filter_h__ +#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; + +typedef enum { + GIT_CRLF_GUESS = -1, + GIT_CRLF_BINARY = 0, + GIT_CRLF_TEXT, + GIT_CRLF_INPUT, + GIT_CRLF_CRLF, + GIT_CRLF_AUTO, +} git_crlf_t; + +typedef struct { + /* NUL, CR, LF and CRLF counts */ + unsigned int nul, cr, lf, crlf; + + /* These are just approximations! */ + unsigned int printable, nonprintable; +} git_text_stats; + +/* + * 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); + +/* + * 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); + + +/* + * PLAINTEXT API + */ + +/* + * Gather stats for a piece of text + * + * Fill the `stats` structure with information on the number of + * unreadable characters, carriage returns, etc, so it can be + * used in heuristics. + */ +extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text); + +/* + * Process `git_text_stats` data generated by `git_text_stat` to see + * if it qualifies as a binary file + */ +extern int git_text_is_binary(git_text_stats *stats); + +#endif diff --git a/src/global.c b/src/global.c new file mode 100644 index 000000000..368c6c664 --- /dev/null +++ b/src/global.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "global.h" +#include "git2/threads.h" +#include "thread-utils.h" + +/** + * Handle the global state with TLS + * + * If libgit2 is built with GIT_THREADS enabled, + * the `git_threads_init()` function must be called + * before calling any other function of the library. + * + * This function allocates a TLS index (using pthreads + * or the native Win32 API) to store the global state + * on a per-thread basis. + * + * Any internal method that requires global state will + * then call `git__global_state()` which returns a pointer + * to the global state structure; this pointer is lazily + * allocated on each thread. + * + * Before shutting down the library, the + * `git_threads_shutdown` method must be called to free + * the previously reserved TLS index. + * + * If libgit2 is built without threading support, the + * `git__global_statestate()` call returns a pointer to a single, + * statically allocated global state. The `git_thread_` + * functions are not available in that case. + */ + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + +static DWORD _tls_index; +static int _tls_init = 0; + +void git_threads_init(void) +{ + if (_tls_init) + return; + + _tls_index = TlsAlloc(); + _tls_init = 1; +} + +void git_threads_shutdown(void) +{ + TlsFree(_tls_index); + _tls_init = 0; +} + +git_global_st *git__global_state(void) +{ + void *ptr; + + if ((ptr = TlsGetValue(_tls_index)) != NULL) + return ptr; + + ptr = git__malloc(sizeof(git_global_st)); + if (!ptr) + return NULL; + + memset(ptr, 0x0, sizeof(git_global_st)); + TlsSetValue(_tls_index, ptr); + return ptr; +} + +#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) + +static pthread_key_t _tls_key; +static int _tls_init = 0; + +static void cb__free_status(void *st) +{ + git__free(st); +} + +void git_threads_init(void) +{ + if (_tls_init) + return; + + pthread_key_create(&_tls_key, &cb__free_status); + _tls_init = 1; +} + +void git_threads_shutdown(void) +{ + pthread_key_delete(_tls_key); + _tls_init = 0; +} + +git_global_st *git__global_state(void) +{ + void *ptr; + + if ((ptr = pthread_getspecific(_tls_key)) != NULL) + return ptr; + + ptr = git__malloc(sizeof(git_global_st)); + if (!ptr) + return NULL; + + memset(ptr, 0x0, sizeof(git_global_st)); + pthread_setspecific(_tls_key, ptr); + return ptr; +} + +#else + +static git_global_st __state; + +void git_threads_init(void) +{ + /* noop */ +} + +void git_threads_shutdown(void) +{ + /* noop */ +} + +git_global_st *git__global_state(void) +{ + return &__state; +} + +#endif /* GIT_THREADS */ diff --git a/src/global.h b/src/global.h new file mode 100644 index 000000000..2b525ce07 --- /dev/null +++ b/src/global.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_global_h__ +#define INCLUDE_global_h__ + +#include "mwindow.h" + +typedef struct { + struct { + char last[1024]; + } error; + + git_error *last_error; + git_error error_t; + + git_mwindow_ctl mem_ctl; +} git_global_st; + +git_global_st *git__global_state(void); + +#define GIT_GLOBAL (git__global_state()) + +#endif diff --git a/src/hash.c b/src/hash.c index 775e4b4c1..460756913 100644 --- a/src/hash.c +++ b/src/hash.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -28,10 +10,8 @@ #if defined(PPC_SHA1) # include "ppc/sha1.h" -#elif defined(OPENSSL_SHA1) -# include #else -# include "block-sha1/sha1.h" +# include "sha1.h" #endif struct git_hash_ctx { @@ -52,7 +32,7 @@ git_hash_ctx *git_hash_new_ctx(void) void git_hash_free_ctx(git_hash_ctx *ctx) { - free(ctx); + git__free(ctx); } void git_hash_init(git_hash_ctx *ctx) diff --git a/src/hash.h b/src/hash.h index 2b769a4c9..33d7b20cd 100644 --- a/src/hash.h +++ b/src/hash.h @@ -1,5 +1,8 @@ /* - * hash.h + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_hash_h__ #define INCLUDE_hash_h__ diff --git a/src/hashtable.c b/src/hashtable.c deleted file mode 100644 index b40737d67..000000000 --- a/src/hashtable.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "repository.h" -#include "commit.h" - -#define MAX_LOOPS 5 -static const double max_load_factor = 0.65; - -static int resize_to(git_hashtable *self, size_t new_size); -static int set_size(git_hashtable *self, size_t new_size); -static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id); -static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other); -static int node_insert(git_hashtable *self, git_hashtable_node *new_node); -static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size); - -static int resize_to(git_hashtable *self, size_t new_size) -{ - git_hashtable_node *old_nodes = self->nodes; - size_t old_size = self->size; - - self->is_resizing = 1; - - do { - self->size = new_size; - self->size_mask = new_size - 1; - self->key_count = 0; - self->nodes = git__calloc(1, sizeof(git_hashtable_node) * self->size); - - if (self->nodes == NULL) - return GIT_ENOMEM; - - if (insert_nodes(self, old_nodes, old_size) == 0) - self->is_resizing = 0; - else { - new_size *= 2; - free(self->nodes); - } - } while(self->is_resizing); - - free(old_nodes); - return GIT_SUCCESS; -} - -static int set_size(git_hashtable *self, size_t new_size) -{ - self->nodes = realloc(self->nodes, new_size * sizeof(git_hashtable_node)); - if (self->nodes == NULL) - return GIT_ENOMEM; - - if (new_size > self->size) { - memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(git_hashtable_node)); - } - - self->size = new_size; - self->size_mask = new_size - 1; - return GIT_SUCCESS; -} - -static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id) -{ - size_t pos = self->hash(key, hash_id) & self->size_mask; - return git_hashtable_node_at(self->nodes, pos); -} - -static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other) -{ - git_hashtable_node tmp = *self; - *self = *other; - *other = tmp; -} - -static int node_insert(git_hashtable *self, git_hashtable_node *new_node) -{ - int iteration, hash_id; - - for (iteration = 0; iteration < MAX_LOOPS; iteration++) { - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - git_hashtable_node *node; - node = node_with_hash(self, new_node->key, hash_id); - node_swap_with(new_node, node); - if(new_node->key == 0x0){ - self->key_count++; - return GIT_SUCCESS; - } - } - } - - if (self->is_resizing) - return git__throw(GIT_EBUSY, "Failed to insert node. Hashtable is currently resizing"); - - resize_to(self, self->size * 2); - git_hashtable_insert(self, new_node->key, new_node->value); - return GIT_SUCCESS; -} - -static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size) -{ - size_t i; - - for (i = 0; i < old_size; ++i) { - git_hashtable_node *node = git_hashtable_node_at(old_nodes, i); - if (node->key && git_hashtable_insert(self, node->key, node->value) < GIT_SUCCESS) - return GIT_ENOMEM; - } - - return GIT_SUCCESS; -} - -git_hashtable *git_hashtable_alloc(size_t min_size, - git_hash_ptr hash, - git_hash_keyeq_ptr key_eq) -{ - git_hashtable *table; - - assert(hash && key_eq); - - if ((table = git__malloc(sizeof(git_hashtable))) == NULL) - return NULL; - - memset(table, 0x0, sizeof(git_hashtable)); - - if (min_size < 8) - min_size = 8; - - /* round up size to closest power of 2 */ - min_size--; - min_size |= min_size >> 1; - min_size |= min_size >> 2; - min_size |= min_size >> 4; - min_size |= min_size >> 8; - min_size |= min_size >> 16; - - table->hash = hash; - table->key_equal = key_eq; - - set_size(table, min_size + 1); - - return table; -} - -void git_hashtable_clear(git_hashtable *self) -{ - assert(self); - - memset(self->nodes, 0x0, sizeof(git_hashtable_node) * self->size); - self->key_count = 0; -} - -void git_hashtable_free(git_hashtable *self) -{ - assert(self); - - free(self->nodes); - free(self); -} - - -int git_hashtable_insert2(git_hashtable *self, const void *key, void *value, void **old_value) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - *old_value = NULL; - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - - if (!node->key) { - node->key = key; - node->value = value; - self->key_count++; - return GIT_SUCCESS; - } - - if (key == node->key || self->key_equal(key, node->key) == 0) { - *old_value = node->value; - node->key = key; - node->value = value; - return GIT_SUCCESS; - } - } - - /* no space in table; must do cuckoo dance */ - { - git_hashtable_node x; - x.key = key; - x.value = value; - return node_insert(self, &x); - } -} - -void *git_hashtable_lookup(git_hashtable *self, const void *key) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - if (node->key && self->key_equal(key, node->key) == 0) - return node->value; - } - - return NULL; -} - -int git_hashtable_remove(git_hashtable *self, const void *key) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - if (node->key && self->key_equal(key, node->key) == 0) { - node->key = NULL; - node->value = NULL; - self->key_count--; - return GIT_SUCCESS; - } - } - - return git__throw(GIT_ENOTFOUND, "Entry not found in hash table"); -} - -int git_hashtable_merge(git_hashtable *self, git_hashtable *other) -{ - if (resize_to(self, (self->size + other->size) * 2) < GIT_SUCCESS) - return GIT_ENOMEM; - - return insert_nodes(self, other->nodes, other->key_count); -} - diff --git a/src/hashtable.h b/src/hashtable.h deleted file mode 100644 index c3475b6ed..000000000 --- a/src/hashtable.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef INCLUDE_hashtable_h__ -#define INCLUDE_hashtable_h__ - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" - -#define GIT_HASHTABLE_HASHES 3 - -typedef uint32_t (*git_hash_ptr)(const void *, int hash_id); -typedef int (*git_hash_keyeq_ptr)(const void *key_a, const void *key_b); - -struct git_hashtable_node { - const void *key; - void *value; -}; - -struct git_hashtable { - struct git_hashtable_node *nodes; - - size_t size_mask; - size_t size; - size_t key_count; - - int is_resizing; - - git_hash_ptr hash; - git_hash_keyeq_ptr key_equal; -}; - -typedef struct git_hashtable_node git_hashtable_node; -typedef struct git_hashtable git_hashtable; - -git_hashtable *git_hashtable_alloc(size_t min_size, - git_hash_ptr hash, - git_hash_keyeq_ptr key_eq); -void *git_hashtable_lookup(git_hashtable *h, const void *key); -int git_hashtable_remove(git_hashtable *table, const void *key); -void git_hashtable_free(git_hashtable *h); -void git_hashtable_clear(git_hashtable *h); -int git_hashtable_merge(git_hashtable *self, git_hashtable *other); - -int git_hashtable_insert2(git_hashtable *h, const void *key, void *value, void **old_value); - -GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *value) -{ - void *_unused; - return git_hashtable_insert2(h, key, value, &_unused); -} - -#define git_hashtable_node_at(nodes, pos) ((git_hashtable_node *)(&nodes[pos])) - -#define GIT_HASHTABLE_FOREACH(self, pkey, pvalue, code) {\ - git_hashtable *_self = (self);\ - git_hashtable_node *_nodes = _self->nodes;\ - unsigned int _i, _size = _self->size;\ - for (_i = 0; _i < _size; _i ++) {\ - git_hashtable_node *_node = git_hashtable_node_at(_nodes, _i);\ - if (_node->key)\ - {\ - pkey = _node->key;\ - pvalue = _node->value;\ - code;\ - }\ - }\ -} - -#define GIT_HASHTABLE_FOREACH_DELETE() {\ - _node->key = NULL; _node->value = NULL; _self->key_count--;\ -} - - -#endif diff --git a/src/ignore.c b/src/ignore.c new file mode 100644 index 000000000..fc6194bb5 --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,203 @@ +#include "ignore.h" +#include "path.h" + +#define GIT_IGNORE_INTERNAL "[internal]exclude" +#define GIT_IGNORE_FILE_INREPO "info/exclude" +#define GIT_IGNORE_FILE ".gitignore" + +static int parse_ignore_file( + git_repository *repo, const char *buffer, git_attr_file *ignores) +{ + int error = 0; + git_attr_fnmatch *match = NULL; + const char *scan = NULL; + char *context = NULL; + + GIT_UNUSED(repo); + + if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) { + context = ignores->key + 2; + context[strlen(context) - strlen(GIT_IGNORE_FILE)] = '\0'; + } + + scan = buffer; + + while (!error && *scan) { + if (!match) { + match = git__calloc(1, sizeof(*match)); + GITERR_CHECK_ALLOC(match); + } + + if (!(error = git_attr_fnmatch__parse( + match, ignores->pool, context, &scan))) + { + match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE; + scan = git__next_line(scan); + error = git_vector_insert(&ignores->rules, match); + } + + if (error != 0) { + git__free(match->pattern); + match->pattern = NULL; + + if (error == GIT_ENOTFOUND) + error = 0; + } else { + match = NULL; /* vector now "owns" the match */ + } + } + + git__free(match); + /* restore file path used for context */ + if (context) + context[strlen(context)] = '.'; /* first char of GIT_IGNORE_FILE */ + + return error; +} + +#define push_ignore_file(R,S,B,F) \ + git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(S)) + +static int push_one_ignore(void *ref, git_buf *path) +{ + git_ignores *ign = (git_ignores *)ref; + return push_ignore_file(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE); +} + +int git_ignore__for_path( + git_repository *repo, + const char *path, + git_ignores *ignores) +{ + int error = 0; + const char *workdir = git_repository_workdir(repo); + + assert(ignores); + + ignores->repo = repo; + git_buf_init(&ignores->dir, 0); + ignores->ign_internal = NULL; + + if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 || + (error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 || + (error = git_attr_cache__init(repo)) < 0) + goto cleanup; + + /* given a unrooted path in a non-bare repo, resolve it */ + if (workdir && git_path_root(path) < 0) + error = git_path_find_dir(&ignores->dir, path, workdir); + else + error = git_buf_sets(&ignores->dir, path); + if (error < 0) + goto cleanup; + + /* set up internals */ + error = git_attr_cache__internal_file( + repo, GIT_IGNORE_INTERNAL, &ignores->ign_internal); + if (error < 0) + goto cleanup; + + /* load .gitignore up the path */ + if (workdir != NULL) { + error = git_path_walk_up( + &ignores->dir, workdir, push_one_ignore, ignores); + if (error < 0) + goto cleanup; + } + + /* load .git/info/exclude */ + error = push_ignore_file(repo, &ignores->ign_global, + git_repository_path(repo), GIT_IGNORE_FILE_INREPO); + if (error < 0) + goto cleanup; + + /* load core.excludesfile */ + if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) + error = push_ignore_file(repo, &ignores->ign_global, NULL, + git_repository_attr_cache(repo)->cfg_excl_file); + +cleanup: + if (error < 0) + git_ignore__free(ignores); + + return error; +} + +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_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) + git_vector_pop(&ign->ign_path); + git_buf_rtruncate_at_char(&ign->dir, '/'); + } + return 0; +} + +void git_ignore__free(git_ignores *ignores) +{ + /* don't need to free ignores->ign_internal since it is in cache */ + git_vector_free(&ignores->ign_path); + git_vector_free(&ignores->ign_global); + git_buf_free(&ignores->dir); +} + +static bool ignore_lookup_in_rules( + git_vector *rules, git_attr_path *path, int *ignored) +{ + unsigned int j; + git_attr_fnmatch *match; + + git_vector_rforeach(rules, j, match) { + if (git_attr_fnmatch__match(match, path)) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + return true; + } + } + + return false; +} + +int git_ignore__lookup( + git_ignores *ignores, const char *pathname, int *ignored) +{ + unsigned int i; + git_attr_file *file; + git_attr_path path; + + if (git_attr_path__init( + &path, pathname, git_repository_workdir(ignores->repo)) < 0) + return -1; + + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules( + &ignores->ign_internal->rules, &path, ignored)) + goto cleanup; + + /* next process files in the path */ + git_vector_foreach(&ignores->ign_path, i, file) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores->ign_global, i, file) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + *ignored = 0; + +cleanup: + git_attr_path__free(&path); + return 0; +} diff --git a/src/ignore.h b/src/ignore.h new file mode 100644 index 000000000..809d2edbd --- /dev/null +++ b/src/ignore.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "repository.h" +#include "vector.h" + +/* The git_ignores structure maintains three sets of ignores: + * - internal ignores + * - per directory ignores + * - global ignores (at lower priority than the others) + * As you traverse from one directory to another, you can push and pop + * directories onto git_ignores list efficiently. + */ +typedef struct { + git_repository *repo; + git_buf dir; + git_attr_file *ign_internal; + git_vector ign_path; + git_vector ign_global; +} git_ignores; + +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); + +extern int git_ignore__pop_dir(git_ignores *ign); + +extern void git_ignore__free(git_ignores *ign); + +extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); + +#endif diff --git a/src/index.c b/src/index.c index c86d37a08..f1ae9a710 100644 --- a/src/index.c +++ b/src/index.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 @@ -28,6 +10,8 @@ #include "common.h" #include "repository.h" #include "index.h" +#include "tree.h" +#include "tree-cache.h" #include "hash.h" #include "git2/odb.h" #include "git2/blob.h" @@ -48,6 +32,8 @@ static const unsigned int INDEX_HEADER_SIG = 0x44495243; static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; +#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) + struct index_header { uint32_t signature; uint32_t version; @@ -98,93 +84,92 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size); static int read_header(struct index_header *dest, const void *buffer); -static int read_tree(git_index *index, const char *buffer, size_t buffer_size); -static int read_tree_internal(git_index_tree **, const char **, const char *, git_index_tree *); - static int parse_index(git_index *index, const char *buffer, size_t buffer_size); static int is_index_extended(git_index *index); -static void sort_index(git_index *index); static int write_index(git_index *index, git_filebuf *file); -int index_srch(const void *key, const void *array_member) -{ - const char *filename = (const char *)key; - const git_index_entry *entry = *(const git_index_entry **)(array_member); +static void index_entry_free(git_index_entry *entry); - return strcmp(filename, entry->path); +static int index_srch(const void *key, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcmp(key, entry->path); } -int index_cmp(const void *a, const void *b) +static int index_cmp(const void *a, const void *b) { - const git_index_entry *entry_a = *(const git_index_entry **)(a); - const git_index_entry *entry_b = *(const git_index_entry **)(b); + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; return strcmp(entry_a->path, entry_b->path); } -int unmerged_srch(const void *key, const void *array_member) +static int unmerged_srch(const void *key, const void *array_member) { - const char *path = (const char *) key; - const git_index_entry_unmerged *entry = *(const git_index_entry_unmerged **) (array_member); + const git_index_entry_unmerged *entry = array_member; - return strcmp(path, entry->path); + return strcmp(key, entry->path); } -int unmerged_cmp(const void *a, const void *b) +static int unmerged_cmp(const void *a, const void *b) { - const git_index_entry_unmerged *info_a = *(const git_index_entry_unmerged **)(a); - const git_index_entry_unmerged *info_b = *(const git_index_entry_unmerged **)(b); + const git_index_entry_unmerged *info_a = a; + const git_index_entry_unmerged *info_b = b; return strcmp(info_a->path, info_b->path); } -static int index_initialize(git_index **index_out, git_repository *owner, const char *index_path) +static unsigned int index_create_mode(unsigned int mode) +{ + if (S_ISLNK(mode)) + return S_IFLNK; + if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) + return (S_IFLNK | S_IFDIR); + return S_IFREG | ((mode & 0100) ? 0755 : 0644); +} + +int git_index_open(git_index **index_out, const char *index_path) { git_index *index; assert(index_out && index_path); - index = git__malloc(sizeof(git_index)); - if (index == NULL) - return GIT_ENOMEM; - - memset(index, 0x0, sizeof(git_index)); + index = git__calloc(1, sizeof(git_index)); + GITERR_CHECK_ALLOC(index); index->index_file_path = git__strdup(index_path); - if (index->index_file_path == NULL) { - free(index); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(index->index_file_path); - index->repository = owner; - - git_vector_init(&index->entries, 32, index_cmp); + if (git_vector_init(&index->entries, 32, index_cmp) < 0) + return -1; /* Check if index file is stored on disk already */ - if (gitfo_exists(index->index_file_path) == 0) + if (git_path_exists(index->index_file_path) == true) index->on_disk = 1; *index_out = index; + GIT_REFCOUNT_INC(index); return git_index_read(index); } -int git_index_open(git_index **index_out, const char *index_path) +static void index_free(git_index *index) { - assert(index_out && index_path); - return index_initialize(index_out, NULL, index_path); -} + git_index_entry *e; + unsigned int i; -/* - * Moved from `repository.c` - */ -int git_repository_index(git_index **index_out, git_repository *repo) -{ - assert(index_out && repo); + git_index_clear(index); + git_vector_foreach(&index->entries, i, e) { + index_entry_free(e); + } + git_vector_free(&index->entries); + git_vector_foreach(&index->unmerged, i, e) { + index_entry_free(e); + } + git_vector_free(&index->unmerged); - if (repo->is_bare) - return git__throw(GIT_EBAREINDEX, "Failed to open index. Repository is bare"); - - return index_initialize(index_out, repo, repo->path_index); + git__free(index->index_file_path); + git__free(index); } void git_index_free(git_index *index) @@ -192,27 +177,7 @@ void git_index_free(git_index *index) if (index == NULL) return; - git_index_clear(index); - git_vector_free(&index->entries); - git_vector_free(&index->unmerged); - - free(index->index_file_path); - free(index); -} - -static void free_tree(git_index_tree *tree) -{ - unsigned int i; - - if (tree == NULL) - return; - - for (i = 0; i < tree->children_count; ++i) - free_tree(tree->children[i]); - - free(tree->name); - free(tree->children); - free(tree); + GIT_REFCOUNT_DEC(index, index_free); } void git_index_clear(git_index *index) @@ -224,90 +189,85 @@ void git_index_clear(git_index *index) for (i = 0; i < index->entries.length; ++i) { git_index_entry *e; e = git_vector_get(&index->entries, i); - free((char *)e->path); - free(e); + git__free(e->path); + git__free(e); } for (i = 0; i < index->unmerged.length; ++i) { git_index_entry_unmerged *e; e = git_vector_get(&index->unmerged, i); - free((char *)e->path); - free(e); + git__free(e->path); + git__free(e); } git_vector_clear(&index->entries); git_vector_clear(&index->unmerged); index->last_modified = 0; - free_tree(index->tree); + git_tree_cache_free(index->tree); index->tree = NULL; } int git_index_read(git_index *index) { - struct stat indexst; - int error = GIT_SUCCESS; + int error, updated; + git_buf buffer = GIT_BUF_INIT; + time_t mtime; assert(index->index_file_path); - if (!index->on_disk || gitfo_exists(index->index_file_path) < 0) { + if (!index->on_disk || git_path_exists(index->index_file_path) == false) { git_index_clear(index); index->on_disk = 0; - return GIT_SUCCESS; + return 0; } - if (gitfo_stat(index->index_file_path, &indexst) < 0) - return git__throw(GIT_EOSERR, "Failed to read index. %s does not exist or is corrupted", index->index_file_path); - - if (!S_ISREG(indexst.st_mode)) - return git__throw(GIT_ENOTFOUND, "Failed to read index. %s is not an index file", index->index_file_path); - - if (indexst.st_mtime != index->last_modified) { - - gitfo_buf buffer; - - if ((error = gitfo_read_file(&buffer, index->index_file_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read index"); + /* We don't want to update the mtime if we fail to parse the index */ + mtime = index->last_modified; + error = git_futils_readbuffer_updated( + &buffer, index->index_file_path, &mtime, &updated); + if (error < 0) + return error; + if (updated) { git_index_clear(index); - error = parse_index(index, buffer.data, buffer.len); + error = parse_index(index, buffer.ptr, buffer.size); - if (error == GIT_SUCCESS) - index->last_modified = indexst.st_mtime; + if (!error) + index->last_modified = mtime; - gitfo_free_buf(&buffer); + git_buf_free(&buffer); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to read index"); return error; } int git_index_write(git_index *index) { - git_filebuf file; + git_filebuf file = GIT_FILEBUF_INIT; struct stat indexst; int error; - sort_index(index); + git_vector_sort(&index->entries); - if ((error = git_filebuf_open(&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if ((error = git_filebuf_open( + &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) + return error; - if ((error = write_index(index, &file)) < GIT_SUCCESS) { + if ((error = write_index(index, &file)) < 0) { git_filebuf_cleanup(&file); - return git__rethrow(error, "Failed to write index"); + return error; } - if ((error = git_filebuf_commit(&file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0) + return error; - if (gitfo_stat(index->index_file_path, &indexst) == 0) { + if (p_stat(index->index_file_path, &indexst) == 0) { index->last_modified = indexst.st_mtime; index->on_disk = 1; } - return GIT_SUCCESS; + return 0; } unsigned int git_index_entrycount(git_index *index) @@ -322,39 +282,110 @@ unsigned int git_index_entrycount_unmerged(git_index *index) return index->unmerged.length; } -git_index_entry *git_index_get(git_index *index, int n) -{ - assert(index); - sort_index(index); - return git_vector_get(&index->entries, (unsigned int)n); -} - -static void sort_index(git_index *index) +git_index_entry *git_index_get(git_index *index, unsigned int n) { git_vector_sort(&index->entries); + return git_vector_get(&index->entries, n); } -static int index_insert(git_index *index, const git_index_entry *source_entry, int replace) +void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry) +{ + entry->ctime.seconds = (git_time_t)st->st_ctime; + entry->mtime.seconds = (git_time_t)st->st_mtime; + /* entry->mtime.nanoseconds = st->st_mtimensec; */ + /* entry->ctime.nanoseconds = st->st_ctimensec; */ + entry->dev = st->st_rdev; + entry->ino = st->st_ino; + entry->mode = index_create_mode(st->st_mode); + entry->uid = st->st_uid; + entry->gid = st->st_gid; + entry->file_size = st->st_size; +} + +static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage) +{ + git_index_entry *entry = NULL; + struct stat st; + git_oid oid; + const char *workdir; + git_buf full_path = GIT_BUF_INIT; + int error; + + assert(stage >= 0 && stage <= 3); + + if (INDEX_OWNER(index) == NULL || + (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL) + { + giterr_set(GITERR_INDEX, + "Could not initialize index entry. Repository is bare"); + return -1; + } + + 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_fromfile(&oid, INDEX_OWNER(index), rel_path)) < 0) + return error; + + entry = git__calloc(1, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(entry); + + git_index__init_entry_from_stat(&st, entry); + + entry->oid = oid; + entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); + entry->path = git__strdup(rel_path); + GITERR_CHECK_ALLOC(entry->path); + + *entry_out = entry; + return 0; +} + +static git_index_entry *index_entry_dup(const git_index_entry *source_entry) { git_index_entry *entry; - size_t path_length; - int position; - - assert(index && source_entry); - - if (source_entry->path == NULL) - return git__throw(GIT_EMISSINGOBJDATA, "Failed to insert into index. Entry has no path"); entry = git__malloc(sizeof(git_index_entry)); - if (entry == NULL) - return GIT_ENOMEM; + if (!entry) + return NULL; memcpy(entry, source_entry, sizeof(git_index_entry)); /* duplicate the path string so we own it */ entry->path = git__strdup(entry->path); - if (entry->path == NULL) - return GIT_ENOMEM; + if (!entry->path) + return NULL; + + 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; + int position; + git_index_entry **entry_array; + + assert(index && entry && entry->path != NULL); /* make sure that the path length flag is correct */ path_length = strlen(entry->path); @@ -366,120 +397,127 @@ static int index_insert(git_index *index, const git_index_entry *source_entry, i else entry->flags |= GIT_IDXENTRY_NAMEMASK;; + /* + * replacing is not requested: just insert entry at the end; + * the index is no longer sorted + */ + if (!replace) + return git_vector_insert(&index->entries, entry); /* look if an entry with this path already exists */ - position = git_index_find(index, source_entry->path); + position = git_index_find(index, entry->path); - /* if no entry exists and replace is not set, - * add the entry at the end; - * the index is no longer sorted */ - if (!replace || position == GIT_ENOTFOUND) { - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + /* + * if no entry exists add the entry at the end; + * the index is no longer sorted + */ + if (position == GIT_ENOTFOUND) + return git_vector_insert(&index->entries, entry); - /* if a previous entry exists and replace is set, - * replace it */ - } else { - git_index_entry **entry_array = (git_index_entry **)index->entries.contents; + /* exists, replace it */ + entry_array = (git_index_entry **) index->entries.contents; + git__free(entry_array[position]->path); + git__free(entry_array[position]); + entry_array[position] = entry; - free((char *)entry_array[position]->path); - free(entry_array[position]); - - entry_array[position] = entry; - } - - return GIT_SUCCESS; + return 0; } -static int index_init_entry(git_index_entry *entry, git_index *index, const char *rel_path, int stage) +static int index_add(git_index *index, const char *path, int stage, int replace) { - char full_path[GIT_PATH_MAX]; - struct stat st; - int error; + git_index_entry *entry = NULL; + int ret; - if (index->repository == NULL) - return git__throw(GIT_EBAREINDEX, "Failed to initialize entry. Repository is bare"); + if ((ret = index_entry_init(&entry, index, path, stage)) < 0 || + (ret = index_insert(index, entry, replace)) < 0) + { + index_entry_free(entry); + return ret; + } - git__joinpath(full_path, index->repository->path_workdir, rel_path); - - if (gitfo_exists(full_path) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to initialize entry. %s does not exist", full_path); - - if (gitfo_stat(full_path, &st) < 0) - return git__throw(GIT_EOSERR, "Failed to initialize entry. %s appears to be corrupted", full_path); - - if (stage < 0 || stage > 3) - return git__throw(GIT_ERROR, "Failed to initialize entry. Invalid stage %i", stage); - - memset(entry, 0x0, sizeof(git_index_entry)); - - entry->ctime.seconds = (git_time_t)st.st_ctime; - entry->mtime.seconds = (git_time_t)st.st_mtime; - /* entry.mtime.nanoseconds = st.st_mtimensec; */ - /* entry.ctime.nanoseconds = st.st_ctimensec; */ - entry->dev= st.st_rdev; - entry->ino = st.st_ino; - entry->mode = st.st_mode; - entry->uid = st.st_uid; - entry->gid = st.st_gid; - entry->file_size = st.st_size; - - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromfile(&entry->oid, index->repository, rel_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize index entry"); - - entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); - entry->path = (char *)rel_path; /* do not duplicate; index_insert already does this */ - return GIT_SUCCESS; + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; } int git_index_add(git_index *index, const char *path, int stage) { - int error; - git_index_entry entry; - - if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to add to index"); - - return index_insert(index, &entry, 1); + return index_add(index, path, stage, 1); } int git_index_append(git_index *index, const char *path, int stage) { - int error; - git_index_entry entry; + return index_add(index, path, stage, 0); +} - if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to append to index"); +static int index_add2( + git_index *index, const git_index_entry *source_entry, int replace) +{ + git_index_entry *entry = NULL; + int ret; - return index_insert(index, &entry, 0); + entry = index_entry_dup(source_entry); + if (entry == NULL) + return -1; + + if ((ret = index_insert(index, entry, replace)) < 0) { + index_entry_free(entry); + return ret; + } + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; } int git_index_add2(git_index *index, const git_index_entry *source_entry) { - return index_insert(index, source_entry, 1); + return index_add2(index, source_entry, 1); } int git_index_append2(git_index *index, const git_index_entry *source_entry) { - return index_insert(index, source_entry, 0); + return index_add2(index, source_entry, 1); } - int git_index_remove(git_index *index, int position) { - assert(index); - sort_index(index); - return git_vector_remove(&index->entries, (unsigned int)position); + int error; + git_index_entry *entry; + + git_vector_sort(&index->entries); + + entry = git_vector_get(&index->entries, position); + if (entry != NULL) + git_tree_cache_invalidate_path(index->tree, entry->path); + + error = git_vector_remove(&index->entries, (unsigned int)position); + + if (!error) + index_entry_free(entry); + + return error; } int git_index_find(git_index *index, const char *path) { - sort_index(index); return git_vector_bsearch2(&index->entries, index_srch, path); } -const git_index_entry_unmerged *git_index_get_unmerged(git_index *index, const char *path) +unsigned int git_index__prefix_position(git_index *index, const char *path) +{ + unsigned int pos; + + git_vector_bsearch3(&pos, &index->entries, index_srch, path); + + return pos; +} + +void git_index_uniq(git_index *index) +{ + git_vector_uniq(&index->entries); +} + +const git_index_entry_unmerged *git_index_get_unmerged_bypath( + git_index *index, const char *path) { int pos; assert(index && path); @@ -487,129 +525,23 @@ const git_index_entry_unmerged *git_index_get_unmerged(git_index *index, const c if (!index->unmerged.length) return NULL; - if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < GIT_SUCCESS) + if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0) return NULL; return git_vector_get(&index->unmerged, pos); } - -static int read_tree_internal(git_index_tree **out, - const char **buffer_in, const char *buffer_end, git_index_tree *parent) +const git_index_entry_unmerged *git_index_get_unmerged_byindex( + git_index *index, unsigned int n) { - git_index_tree *tree; - const char *name_start, *buffer; - long count; - int error = GIT_SUCCESS; - - if ((tree = git__malloc(sizeof(git_index_tree))) == NULL) - return GIT_ENOMEM; - - memset(tree, 0x0, sizeof(git_index_tree)); - tree->parent = parent; - - buffer = name_start = *buffer_in; - - if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* NUL-terminated tree name */ - tree->name = git__strdup(name_start); - if (tree->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } - - if (++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || count < -1) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Invalidated TREE. Free the tree but report success */ - if (count == -1) { - /* FIXME: return buffer_end or the end position for - * this single tree entry */ - *buffer_in = buffer_end; - *out = NULL; - free_tree(tree); /* Needs to be done manually */ - return GIT_SUCCESS; - } - - tree->entries = (size_t)count; - - if (*buffer != ' ' || ++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Number of children of the tree, newline-terminated */ - if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || - count < 0) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - tree->children_count = (size_t)count; - - if (*buffer != '\n' || ++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* 160-bit SHA-1 for this tree and it's children */ - if (buffer + GIT_OID_RAWSZ > buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - git_oid_mkraw(&tree->oid, (const unsigned char *)buffer); - buffer += GIT_OID_RAWSZ; - - /* Parse children: */ - if (tree->children_count > 0) { - unsigned int i; - int err; - - tree->children = git__malloc(tree->children_count * sizeof(git_index_tree *)); - if (tree->children == NULL) - goto cleanup; - - for (i = 0; i < tree->children_count; ++i) { - err = read_tree_internal(&tree->children[i], &buffer, buffer_end, tree); - - if (err < GIT_SUCCESS) - goto cleanup; - } - } - - *buffer_in = buffer; - *out = tree; - return GIT_SUCCESS; - - cleanup: - free_tree(tree); - return error; + assert(index); + return git_vector_get(&index->unmerged, n); } -static int read_tree(git_index *index, const char *buffer, size_t buffer_size) +static int index_error_invalid(const char *message) { - const char *buffer_end = buffer + buffer_size; - int error; - - error = read_tree_internal(&index->tree, &buffer, buffer_end, NULL); - - if (buffer < buffer_end) - return GIT_EOBJCORRUPTED; - - return error; + giterr_set(GITERR_INDEX, "Invalid data in index - %s", message); + return -1; } static int read_unmerged(git_index *index, const char *buffer, size_t size) @@ -618,53 +550,62 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size) size_t len; int i; - git_vector_init(&index->unmerged, 16, unmerged_cmp); + if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0) + return -1; while (size) { git_index_entry_unmerged *lost; len = strlen(buffer) + 1; if (size <= len) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + return index_error_invalid("reading unmerged entries"); - if ((lost = git__malloc(sizeof(git_index_entry_unmerged))) == NULL) - return GIT_ENOMEM; + lost = git__malloc(sizeof(git_index_entry_unmerged)); + GITERR_CHECK_ALLOC(lost); - if (git_vector_insert(&index->unmerged, lost) < GIT_SUCCESS) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + if (git_vector_insert(&index->unmerged, lost) < 0) + return -1; + /* read NUL-terminated pathname for entry */ lost->path = git__strdup(buffer); - if (!lost->path) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(lost->path); size -= len; buffer += len; + /* read 3 ASCII octal numbers for stage entries */ for (i = 0; i < 3; i++) { - if (git__strtol32((long int *) &lost->mode[i], buffer, &endptr, 8) < GIT_SUCCESS || - !endptr || endptr == buffer || *endptr) - return GIT_ERROR; + int tmp; - len = (endptr + 1) - (char *) buffer; + if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || + !endptr || endptr == buffer || *endptr || + (unsigned)tmp > UINT_MAX) + return index_error_invalid("reading unmerged entry stage"); + + lost->mode[i] = tmp; + + len = (endptr + 1) - buffer; if (size <= len) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + return index_error_invalid("reading unmerged entry stage"); size -= len; buffer += len; } + /* read up to 3 OIDs for stage entries */ for (i = 0; i < 3; i++) { if (!lost->mode[i]) continue; if (size < 20) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); - git_oid_mkraw(&lost->oid[i], (unsigned char *) buffer); + return index_error_invalid("reading unmerged entry oid"); + + git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); size -= 20; buffer += 20; } } - return GIT_SUCCESS; + return 0; } static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size) @@ -672,15 +613,13 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe size_t path_length, entry_size; uint16_t flags_raw; const char *path_ptr; - const struct entry_short *source; + const struct entry_short *source = buffer; if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size) return 0; memset(dest, 0x0, sizeof(git_index_entry)); - source = (const struct entry_short *)(buffer); - dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds); dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds); dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds); @@ -695,7 +634,7 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe dest->flags = ntohs(source->flags); if (dest->flags & GIT_IDXENTRY_EXTENDED) { - struct entry_long *source_l = (struct entry_long *)source; + const struct entry_long *source_l = (const struct entry_long *)source; path_ptr = source_l->path; flags_raw = ntohs(source_l->flags_extended); @@ -712,7 +651,7 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe path_end = memchr(path_ptr, '\0', buffer_size); if (path_end == NULL) - return 0; + return 0; path_length = path_end - path_ptr; } @@ -733,20 +672,19 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe static int read_header(struct index_header *dest, const void *buffer) { - const struct index_header *source; - source = (const struct index_header *)(buffer); + const struct index_header *source = buffer; dest->signature = ntohl(source->signature); if (dest->signature != INDEX_HEADER_SIG) - return GIT_EOBJCORRUPTED; + return index_error_invalid("incorrect header signature"); dest->version = ntohl(source->version); if (dest->version != INDEX_VERSION_NUMBER_EXT && dest->version != INDEX_VERSION_NUMBER) - return GIT_EOBJCORRUPTED; + return index_error_invalid("incorrect header version"); dest->entry_count = ntohl(source->entry_count); - return GIT_SUCCESS; + return 0; } static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size) @@ -769,10 +707,10 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { /* tree cache */ if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { - if (read_tree(index, buffer + 8, dest.extension_size) < GIT_SUCCESS) + if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0) return 0; } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { - if (read_unmerged(index, buffer + 8, dest.extension_size) < GIT_SUCCESS) + if (read_unmerged(index, buffer + 8, dest.extension_size) < 0) return 0; } /* else, unsupported extension. We cannot parse this, but we can skip @@ -794,21 +732,21 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) #define seek_forward(_increase) { \ if (_increase >= buffer_size) \ - return git__throw(GIT_EOBJCORRUPTED, "Failed to seek forward. Buffer size exceeded"); \ + return index_error_invalid("ran out of data while parsing"); \ buffer += _increase; \ buffer_size -= _increase;\ } if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer too small"); + return index_error_invalid("insufficient buffer space"); /* Precalculate the SHA1 of the files's contents -- we'll match it to * the provided SHA1 in the footer */ - git_hash_buf(&checksum_calculated, (const void *)buffer, buffer_size - INDEX_FOOTER_SIZE); + git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE); /* Parse header */ - if (read_header(&header, buffer) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header is corrupted"); + if (read_header(&header, buffer) < 0) + return -1; seek_forward(INDEX_HEADER_SIZE); @@ -820,23 +758,22 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) git_index_entry *entry; entry = git__malloc(sizeof(git_index_entry)); - if (entry == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(entry); entry_size = read_entry(entry, buffer, buffer_size); /* 0 bytes read means an object corruption */ if (entry_size == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Entry size is zero"); + return index_error_invalid("invalid entry"); - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&index->entries, entry) < 0) + return -1; seek_forward(entry_size); } if (i != header.entry_count) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header entries changed while parsing"); + return index_error_invalid("header entries changed while parsing"); /* There's still space for some extensions! */ while (buffer_size > INDEX_FOOTER_SIZE) { @@ -846,48 +783,49 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* see if we have read any bytes from the extension */ if (extension_size == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Extension size is zero"); + return index_error_invalid("extension size is zero"); seek_forward(extension_size); } if (buffer_size != INDEX_FOOTER_SIZE) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer size does not match index footer size"); + return index_error_invalid("buffer size does not match index footer size"); /* 160-bit SHA-1 over the content of the index file before this checksum. */ - git_oid_mkraw(&checksum_expected, (const unsigned char *)buffer); + git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer); if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Calculated checksum does not match expected checksum"); + return index_error_invalid("calculated checksum does not match expected"); #undef seek_forward /* force sorting in the vector: the entries are * assured to be sorted on the index */ index->entries.sorted = 1; - return GIT_SUCCESS; + return 0; } static int is_index_extended(git_index *index) { unsigned int i, extended; + git_index_entry *entry; extended = 0; - for (i = 0; i < index->entries.length; ++i) { - git_index_entry *entry; - entry = git_vector_get(&index->entries, i); + git_vector_foreach(&index->entries, i, entry) { entry->flags &= ~GIT_IDXENTRY_EXTENDED; if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { extended++; entry->flags |= GIT_IDXENTRY_EXTENDED; } } + return extended; } static int write_disk_entry(git_filebuf *file, git_index_entry *entry) { + void *mem = NULL; struct entry_short *ondisk; size_t path_len, disk_size; char *path; @@ -899,21 +837,33 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) else disk_size = short_entry_size(path_len); - if (git_filebuf_reserve(file, (void **)&ondisk, disk_size) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_filebuf_reserve(file, &mem, disk_size) < 0) + return -1; + + ondisk = (struct entry_short *)mem; memset(ondisk, 0x0, disk_size); - ondisk->ctime.seconds = htonl((unsigned long)entry->ctime.seconds); - ondisk->mtime.seconds = htonl((unsigned long)entry->mtime.seconds); + /** + * Yes, we have to truncate. + * + * The on-disk format for Index entries clearly defines + * the time and size fields to be 4 bytes each -- so even if + * we store these values with 8 bytes on-memory, they must + * be truncated to 4 bytes before writing to disk. + * + * In 2038 I will be either too dead or too rich to care about this + */ + ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds); + ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds); ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds); ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds); - ondisk->dev = htonl(entry->dev); - ondisk->ino = htonl(entry->ino); + ondisk->dev = htonl(entry->dev); + ondisk->ino = htonl(entry->ino); ondisk->mode = htonl(entry->mode); - ondisk->uid = htonl(entry->uid); - ondisk->gid = htonl(entry->gid); - ondisk->file_size = htonl((unsigned long)entry->file_size); + ondisk->uid = htonl(entry->uid); + ondisk->gid = htonl(entry->gid); + ondisk->file_size = htonl((uint32_t)entry->file_size); git_oid_cpy(&ondisk->oid, &entry->oid); @@ -930,7 +880,7 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) memcpy(path, entry->path, path_len); - return GIT_SUCCESS; + return 0; } static int write_entries(git_index *index, git_filebuf *file) @@ -940,16 +890,15 @@ static int write_entries(git_index *index, git_filebuf *file) for (i = 0; i < index->entries.length; ++i) { git_index_entry *entry; entry = git_vector_get(&index->entries, i); - if (write_disk_entry(file, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (write_disk_entry(file, entry) < 0) + return -1; } - return GIT_SUCCESS; + return 0; } static int write_index(git_index *index, git_filebuf *file) { - int error = GIT_SUCCESS; git_oid hash_final; struct index_header header; @@ -964,11 +913,11 @@ static int write_index(git_index *index, git_filebuf *file) header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); header.entry_count = htonl(index->entries.length); - git_filebuf_write(file, &header, sizeof(struct index_header)); + if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) + return -1; - error = write_entries(index, file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if (write_entries(index, file) < 0) + return -1; /* TODO: write extensions (tree cache) */ @@ -976,12 +925,45 @@ static int write_index(git_index *index, git_filebuf *file) git_filebuf_hash(&hash_final, file); /* write it at the end of the file */ - git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write index"); + return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); } int git_index_entry_stage(const git_index_entry *entry) { return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; } + +static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data) +{ + git_index *index = data; + git_index_entry *entry = NULL; + git_buf path = GIT_BUF_INIT; + + if (git_tree_entry__is_tree(tentry)) + return 0; + + if (git_buf_joinpath(&path, root, tentry->filename) < 0) + return -1; + + entry = git__calloc(1, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->mode = tentry->attr; + entry->oid = tentry->oid; + entry->path = git_buf_detach(&path); + git_buf_free(&path); + + if (index_insert(index, entry, 0) < 0) { + index_entry_free(entry); + return -1; + } + + return 0; +} + +int git_index_read_tree(git_index *index, git_tree *tree) +{ + git_index_clear(index); + + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); +} diff --git a/src/index.h b/src/index.h index f2402fd71..8515f4fcb 100644 --- a/src/index.h +++ b/src/index.h @@ -1,36 +1,38 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_index_h__ #define INCLUDE_index_h__ #include "fileops.h" #include "filebuf.h" #include "vector.h" +#include "tree-cache.h" #include "git2/odb.h" #include "git2/index.h" -struct git_index_tree { - char *name; - - struct git_index_tree *parent; - struct git_index_tree **children; - size_t children_count; - - size_t entries; - git_oid oid; -}; - -typedef struct git_index_tree git_index_tree; +#define GIT_INDEX_FILE "index" +#define GIT_INDEX_FILE_MODE 0666 struct git_index { - git_repository *repository; + git_refcount rc; + char *index_file_path; time_t last_modified; git_vector entries; unsigned int on_disk:1; - git_index_tree *tree; + git_tree_cache *tree; git_vector unmerged; }; +extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry); + +extern unsigned int git_index__prefix_position(git_index *index, const char *path); + #endif diff --git a/src/indexer.c b/src/indexer.c new file mode 100644 index 000000000..f0e0a6381 --- /dev/null +++ b/src/indexer.c @@ -0,0 +1,898 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2/indexer.h" +#include "git2/object.h" +#include "git2/oid.h" + +#include "common.h" +#include "pack.h" +#include "mwindow.h" +#include "posix.h" +#include "pack.h" +#include "filebuf.h" +#include "sha1.h" + +#define UINT31_MAX (0x7FFFFFFF) + +struct entry { + git_oid oid; + uint32_t crc; + uint32_t offset; + uint64_t offset_long; +}; + +struct git_indexer { + struct git_pack_file *pack; + size_t nr_objects; + git_vector objects; + git_filebuf file; + unsigned int fanout[256]; + git_oid hash; +}; + +struct git_indexer_stream { + unsigned int parsed_header :1, + opened_pack; + struct git_pack_file *pack; + git_filebuf pack_file; + git_filebuf index_file; + git_off_t off; + size_t nr_objects; + git_vector objects; + git_vector deltas; + unsigned int fanout[256]; + git_oid hash; +}; + +struct delta_info { + git_off_t delta_off; +}; + +const git_oid *git_indexer_hash(git_indexer *idx) +{ + return &idx->hash; +} + +const git_oid *git_indexer_stream_hash(git_indexer_stream *idx) +{ + return &idx->hash; +} + +static int open_pack(struct git_pack_file **out, const char *filename) +{ + size_t namelen; + struct git_pack_file *pack; + struct stat st; + int fd; + + namelen = strlen(filename); + pack = git__calloc(1, sizeof(struct git_pack_file) + namelen + 1); + GITERR_CHECK_ALLOC(pack); + + memcpy(pack->pack_name, filename, namelen + 1); + + if (p_stat(filename, &st) < 0) { + giterr_set(GITERR_OS, "Failed to stat packfile."); + goto cleanup; + } + + if ((fd = p_open(pack->pack_name, O_RDONLY)) < 0) { + giterr_set(GITERR_OS, "Failed to open packfile."); + goto cleanup; + } + + pack->mwf.fd = fd; + pack->mwf.size = (git_off_t)st.st_size; + + *out = pack; + return 0; + +cleanup: + git__free(pack); + return -1; +} + +static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) +{ + int error; + + /* Verify we recognize this pack file format. */ + if ((error = p_read(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { + giterr_set(GITERR_OS, "Failed to read in pack header"); + return error; + } + + if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { + giterr_set(GITERR_INDEXER, "Wrong pack signature"); + return -1; + } + + if (!pack_version_ok(hdr->hdr_version)) { + giterr_set(GITERR_INDEXER, "Wrong pack version"); + return -1; + } + + return 0; +} + +static int objects_cmp(const void *a, const void *b) +{ + const struct entry *entrya = a; + const struct entry *entryb = b; + + return git_oid_cmp(&entrya->oid, &entryb->oid); +} + +static int cache_cmp(const void *a, const void *b) +{ + const struct git_pack_entry *ea = a; + const struct git_pack_entry *eb = b; + + return git_oid_cmp(&ea->sha1, &eb->sha1); +} + +int git_indexer_stream_new(git_indexer_stream **out, const char *prefix) +{ + git_indexer_stream *idx; + git_buf path = GIT_BUF_INIT; + static const char suff[] = "/objects/pack/pack-received"; + int error; + + idx = git__calloc(1, sizeof(git_indexer_stream)); + GITERR_CHECK_ALLOC(idx); + + 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_buf_free(&path); + if (error < 0) + goto cleanup; + + *out = idx; + return 0; + +cleanup: + git_buf_free(&path); + git_filebuf_cleanup(&idx->pack_file); + git__free(idx); + return -1; +} + +/* Try to store the delta so we can try to resolve it later */ +static int store_delta(git_indexer_stream *idx) +{ + git_otype type; + git_mwindow *w = NULL; + git_mwindow_file *mwf = &idx->pack->mwf; + git_off_t entry_start = idx->off; + struct delta_info *delta; + size_t entry_size; + git_rawobj obj; + int error; + + /* + * ref-delta objects can refer to object that we haven't + * found yet, so give it another opportunity + */ + if (git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off) < 0) + return -1; + + git_mwindow_close(&w); + + /* If it's not a delta, mark it as failure, we can't do anything with it */ + if (type != GIT_OBJ_REF_DELTA && type != GIT_OBJ_OFS_DELTA) + return -1; + + if (type == GIT_OBJ_REF_DELTA) { + idx->off += GIT_OID_RAWSZ; + } else { + git_off_t base_off; + + base_off = get_delta_base(idx->pack, &w, &idx->off, type, entry_start); + git_mwindow_close(&w); + if (base_off < 0) + return (int)base_off; + } + + error = packfile_unpack_compressed(&obj, idx->pack, &w, &idx->off, entry_size, type); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return GIT_EBUFS; + } else if (error < 0){ + return -1; + } + + delta = git__calloc(1, sizeof(struct delta_info)); + GITERR_CHECK_ALLOC(delta); + delta->delta_off = entry_start; + + git__free(obj.data); + + if (git_vector_insert(&idx->deltas, delta) < 0) + return -1; + + return 0; +} + +static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) +{ + int i; + git_oid oid; + void *packed; + size_t entry_size; + unsigned int left; + struct entry *entry; + git_mwindow *w = NULL; + git_mwindow_file *mwf = &idx->pack->mwf; + struct git_pack_entry *pentry; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + /* FIXME: Parse the object instead of hashing it */ + if (git_odb__hashobj(&oid, obj) < 0) { + giterr_set(GITERR_INDEXER, "Failed to hash object"); + return -1; + } + + pentry = git__malloc(sizeof(struct git_pack_entry)); + GITERR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + if (git_vector_insert(&idx->pack->cache, pentry) < 0) + goto on_error; + + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(idx->off - entry_start); + packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left); + if (packed == NULL) + goto on_error; + + entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size)); + git_mwindow_close(&w); + + /* 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; + +on_error: + git__free(entry); + git__free(pentry); + git__free(obj->data); + return -1; +} + +int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats) +{ + int error; + struct git_pack_header hdr; + size_t processed = stats->processed; + git_mwindow_file *mwf = &idx->pack->mwf; + + assert(idx && data && stats); + + if (git_filebuf_write(&idx->pack_file, data, size) < 0) + return -1; + + /* Make sure we set the new size of the pack */ + if (idx->opened_pack) { + idx->pack->mwf.size += size; + //printf("\nadding %zu for %zu\n", size, idx->pack->mwf.size); + } else { + if (open_pack(&idx->pack, idx->pack_file.path_lock) < 0) + return -1; + idx->opened_pack = 1; + mwf = &idx->pack->mwf; + if (git_mwindow_file_register(&idx->pack->mwf) < 0) + return -1; + + return 0; + } + + if (!idx->parsed_header) { + if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) + return 0; + + if (parse_header(&hdr, idx->pack) < 0) + return -1; + + idx->parsed_header = 1; + idx->nr_objects = ntohl(hdr.hdr_entries); + idx->off = sizeof(struct git_pack_header); + + /* for now, limit to 2^32 objects */ + assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)); + + if (git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp) < 0) + return -1; + + idx->pack->has_cache = 1; + if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0) + return -1; + + if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0) + return -1; + + stats->total = (unsigned int)idx->nr_objects; + stats->processed = 0; + } + + /* Now that we have data in the pack, let's try to parse it */ + + /* As the file grows any windows we try to use will be out of date */ + git_mwindow_free_all(mwf); + while (processed < idx->nr_objects) { + git_rawobj obj; + git_off_t entry_start = idx->off; + + if (idx->pack->mwf.size <= idx->off + 20) + return 0; + + error = git_packfile_unpack(&obj, idx->pack, &idx->off); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return 0; + } + + if (error < 0) { + idx->off = entry_start; + error = store_delta(idx); + if (error == GIT_EBUFS) + return 0; + if (error < 0) + return error; + + continue; + } + + if (hash_and_save(idx, &obj, entry_start) < 0) + goto on_error; + + git__free(obj.data); + + stats->processed = (unsigned int)++processed; + } + + return 0; + +on_error: + git_mwindow_free_all(mwf); + return -1; +} + +static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix) +{ + const char prefix[] = "pack-"; + size_t slash = (size_t)path->size; + + /* search backwards for '/' */ + while (slash > 0 && path->ptr[slash - 1] != '/') + slash--; + + if (git_buf_grow(path, slash + 1 + strlen(prefix) + + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; + + git_buf_truncate(path, slash); + git_buf_puts(path, prefix); + git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash); + path->size += GIT_OID_HEXSZ; + git_buf_puts(path, suffix); + + return git_buf_oom(path) ? -1 : 0; +} + +static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats) +{ + unsigned int i; + struct delta_info *delta; + + 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) + return -1; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + return -1; + + git__free(obj.data); + stats->processed++; + } + + return 0; +} + +int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *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; + SHA_CTX ctx; + + /* 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: junk at the end of the pack"); + return -1; + } + + if (idx->deltas.length > 0) + if (resolve_deltas(idx, stats) < 0) + return -1; + + if (stats->processed != stats->total) { + giterr_set(GITERR_INDEXER, "Indexing error: early EOF"); + 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_puts(&filename, "idx"); + if (git_buf_oom(&filename)) + return -1; + + if (git_filebuf_open(&idx->index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0) + goto on_error; + + /* Write out the header */ + hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); + hdr.idx_version = htonl(2); + git_filebuf_write(&idx->index_file, &hdr, sizeof(hdr)); + + /* Write out the fanout table */ + for (i = 0; i < 256; ++i) { + uint32_t n = htonl(idx->fanout[i]); + git_filebuf_write(&idx->index_file, &n, sizeof(n)); + } + + /* Write out the object names (SHA-1 hashes) */ + SHA1_Init(&ctx); + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid)); + SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); + } + SHA1_Final(idx->hash.id, &ctx); + + /* Write out the CRC32 values */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&idx->index_file, &entry->crc, sizeof(uint32_t)); + } + + /* Write out the offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t n; + + if (entry->offset == UINT32_MAX) + n = htonl(0x80000000 | long_offsets++); + else + n = htonl(entry->offset); + + git_filebuf_write(&idx->index_file, &n, sizeof(uint32_t)); + } + + /* Write out the long offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t split[2]; + + if (entry->offset != UINT32_MAX) + continue; + + split[0] = htonl(entry->offset_long >> 32); + split[1] = htonl(entry->offset_long & 0xffffffff); + + git_filebuf_write(&idx->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(&idx->index_file, &file_hash, sizeof(git_oid)); + + /* Write out the packfile trailer to the idx file as well */ + if (git_filebuf_hash(&file_hash, &idx->index_file) < 0) + goto on_error; + + git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid)); + + /* Figure out what the final name should be */ + if (index_path_stream(&filename, idx, ".idx") < 0) + goto on_error; + + /* Commit file */ + if (git_filebuf_commit_at(&idx->index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) + goto on_error; + + git_mwindow_free_all(&idx->pack->mwf); + p_close(idx->pack->mwf.fd); + + if (index_path_stream(&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) + return -1; + + git_buf_free(&filename); + return 0; + +on_error: + git_mwindow_free_all(&idx->pack->mwf); + p_close(idx->pack->mwf.fd); + git_filebuf_cleanup(&idx->index_file); + git_buf_free(&filename); + return -1; +} + +void git_indexer_stream_free(git_indexer_stream *idx) +{ + unsigned int i; + struct entry *e; + struct git_pack_entry *pe; + struct delta_info *delta; + + if (idx == NULL) + return; + + git_vector_foreach(&idx->objects, i, e) + git__free(e); + git_vector_free(&idx->objects); + git_vector_foreach(&idx->pack->cache, i, pe) + git__free(pe); + git_vector_free(&idx->pack->cache); + git_vector_foreach(&idx->deltas, i, delta) + git__free(delta); + git_vector_free(&idx->deltas); + git__free(idx->pack); + git__free(idx); +} + +int git_indexer_new(git_indexer **out, const char *packname) +{ + git_indexer *idx; + struct git_pack_header hdr; + int error; + + assert(out && packname); + + if (git_path_root(packname) < 0) { + giterr_set(GITERR_INDEXER, "Path is not absolute"); + return -1; + } + + idx = git__calloc(1, sizeof(git_indexer)); + GITERR_CHECK_ALLOC(idx); + + open_pack(&idx->pack, packname); + + if ((error = parse_header(&hdr, idx->pack)) < 0) + goto cleanup; + + idx->nr_objects = ntohl(hdr.hdr_entries); + + /* for now, limit to 2^32 objects */ + assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)); + + error = git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp); + if (error < 0) + goto cleanup; + + idx->pack->has_cache = 1; + error = git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp); + if (error < 0) + goto cleanup; + + *out = idx; + + return 0; + +cleanup: + git_indexer_free(idx); + + return -1; +} + +static int index_path(git_buf *path, git_indexer *idx) +{ + const char prefix[] = "pack-", suffix[] = ".idx"; + size_t slash = (size_t)path->size; + + /* search backwards for '/' */ + while (slash > 0 && path->ptr[slash - 1] != '/') + slash--; + + if (git_buf_grow(path, slash + 1 + strlen(prefix) + + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; + + git_buf_truncate(path, slash); + git_buf_puts(path, prefix); + git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash); + path->size += GIT_OID_HEXSZ; + git_buf_puts(path, suffix); + + return git_buf_oom(path) ? -1 : 0; +} + +int git_indexer_write(git_indexer *idx) +{ + git_mwindow *w = NULL; + int error; + 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; + SHA_CTX ctx; + + git_vector_sort(&idx->objects); + + git_buf_sets(&filename, idx->pack->pack_name); + git_buf_truncate(&filename, filename.size - strlen("pack")); + git_buf_puts(&filename, "idx"); + if (git_buf_oom(&filename)) + return -1; + + error = git_filebuf_open(&idx->file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS); + if (error < 0) + goto cleanup; + + /* Write out the header */ + hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); + hdr.idx_version = htonl(2); + error = git_filebuf_write(&idx->file, &hdr, sizeof(hdr)); + if (error < 0) + goto cleanup; + + /* Write out the fanout table */ + for (i = 0; i < 256; ++i) { + uint32_t n = htonl(idx->fanout[i]); + error = git_filebuf_write(&idx->file, &n, sizeof(n)); + if (error < 0) + goto cleanup; + } + + /* Write out the object names (SHA-1 hashes) */ + SHA1_Init(&ctx); + git_vector_foreach(&idx->objects, i, entry) { + error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid)); + SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); + if (error < 0) + goto cleanup; + } + SHA1_Final(idx->hash.id, &ctx); + + /* Write out the CRC32 values */ + git_vector_foreach(&idx->objects, i, entry) { + error = git_filebuf_write(&idx->file, &entry->crc, sizeof(uint32_t)); + if (error < 0) + goto cleanup; + } + + /* Write out the offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t n; + + if (entry->offset == UINT32_MAX) + n = htonl(0x80000000 | long_offsets++); + else + n = htonl(entry->offset); + + error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t)); + if (error < 0) + goto cleanup; + } + + /* Write out the long offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t split[2]; + + if (entry->offset != UINT32_MAX) + continue; + + split[0] = htonl(entry->offset_long >> 32); + split[1] = htonl(entry->offset_long & 0xffffffff); + + error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2); + if (error < 0) + goto cleanup; + } + + /* 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); + git_mwindow_close(&w); + if (packfile_hash == NULL) { + error = -1; + goto cleanup; + } + + memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ); + + git_mwindow_close(&w); + + error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid)); + if (error < 0) + goto cleanup; + + /* Write out the index sha */ + error = git_filebuf_hash(&file_hash, &idx->file); + if (error < 0) + goto cleanup; + + error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid)); + if (error < 0) + goto cleanup; + + /* Figure out what the final name should be */ + error = index_path(&filename, idx); + if (error < 0) + goto cleanup; + + /* Commit file */ + error = git_filebuf_commit_at(&idx->file, filename.ptr, GIT_PACK_FILE_MODE); + +cleanup: + git_mwindow_free_all(&idx->pack->mwf); + if (error < 0) + git_filebuf_cleanup(&idx->file); + git_buf_free(&filename); + + return error; +} + +int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) +{ + git_mwindow_file *mwf; + git_off_t off = sizeof(struct git_pack_header); + int error; + struct entry *entry; + unsigned int left, processed; + + assert(idx && stats); + + mwf = &idx->pack->mwf; + error = git_mwindow_file_register(mwf); + if (error < 0) + return error; + + stats->total = (unsigned int)idx->nr_objects; + stats->processed = processed = 0; + + while (processed < idx->nr_objects) { + git_rawobj obj; + git_oid oid; + struct git_pack_entry *pentry; + git_mwindow *w = NULL; + int i; + git_off_t entry_start = off; + void *packed; + size_t entry_size; + char fmt[GIT_OID_HEXSZ] = {0}; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + + if (off > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = off; + } else { + entry->offset = (uint32_t)off; + } + + error = git_packfile_unpack(&obj, idx->pack, &off); + if (error < 0) + goto cleanup; + + /* FIXME: Parse the object instead of hashing it */ + error = git_odb__hashobj(&oid, &obj); + if (error < 0) { + giterr_set(GITERR_INDEXER, "Failed to hash object"); + goto cleanup; + } + + pentry = git__malloc(sizeof(struct git_pack_entry)); + if (pentry == NULL) { + error = -1; + goto cleanup; + } + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + git_oid_fmt(fmt, &oid); + error = git_vector_insert(&idx->pack->cache, pentry); + if (error < 0) + goto cleanup; + + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(off - entry_start); + packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left); + if (packed == NULL) { + error = -1; + goto cleanup; + } + entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size)); + git_mwindow_close(&w); + + /* Add the object to the list */ + error = git_vector_insert(&idx->objects, entry); + if (error < 0) + goto cleanup; + + for (i = oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + git__free(obj.data); + + stats->processed = ++processed; + } + +cleanup: + git_mwindow_free_all(mwf); + + return error; + +} + +void git_indexer_free(git_indexer *idx) +{ + unsigned int i; + struct entry *e; + struct git_pack_entry *pe; + + if (idx == NULL) + return; + + p_close(idx->pack->mwf.fd); + git_vector_foreach(&idx->objects, i, e) + git__free(e); + git_vector_free(&idx->objects); + git_vector_foreach(&idx->pack->cache, i, pe) + git__free(pe); + git_vector_free(&idx->pack->cache); + git__free(idx->pack); + git__free(idx); +} + diff --git a/src/iterator.c b/src/iterator.c new file mode 100644 index 000000000..819b0e22a --- /dev/null +++ b/src/iterator.c @@ -0,0 +1,748 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "iterator.h" +#include "tree.h" +#include "ignore.h" +#include "buffer.h" +#include "git2/submodule.h" + +#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \ + (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \ + GITERR_CHECK_ALLOC(P); \ + (P)->base.type = GIT_ITERATOR_ ## NAME_UC; \ + (P)->base.start = start ? git__strdup(start) : NULL; \ + (P)->base.end = end ? git__strdup(end) : NULL; \ + (P)->base.current = NAME_LC ## _iterator__current; \ + (P)->base.at_end = NAME_LC ## _iterator__at_end; \ + (P)->base.advance = NAME_LC ## _iterator__advance; \ + (P)->base.seek = NAME_LC ## _iterator__seek; \ + (P)->base.reset = NAME_LC ## _iterator__reset; \ + (P)->base.free = NAME_LC ## _iterator__free; \ + if ((start && !(P)->base.start) || (end && !(P)->base.end)) \ + return -1; \ + } while (0) + + +static int empty_iterator__no_item( + git_iterator *iter, const git_index_entry **entry) +{ + GIT_UNUSED(iter); + *entry = NULL; + return 0; +} + +static int empty_iterator__at_end(git_iterator *iter) +{ + GIT_UNUSED(iter); + return 1; +} + +static int empty_iterator__noop(git_iterator *iter) +{ + GIT_UNUSED(iter); + return 0; +} + +static int empty_iterator__seek(git_iterator *iter, const char *prefix) +{ + GIT_UNUSED(iter); + GIT_UNUSED(prefix); + return -1; +} + +static void empty_iterator__free(git_iterator *iter) +{ + GIT_UNUSED(iter); +} + +int git_iterator_for_nothing(git_iterator **iter) +{ + git_iterator *i = git__calloc(1, sizeof(git_iterator)); + GITERR_CHECK_ALLOC(i); + + i->type = GIT_ITERATOR_EMPTY; + i->current = empty_iterator__no_item; + i->at_end = empty_iterator__at_end; + i->advance = empty_iterator__no_item; + i->seek = empty_iterator__seek; + i->reset = empty_iterator__noop; + i->free = empty_iterator__free; + + *iter = i; + + return 0; +} + + +typedef struct tree_iterator_frame tree_iterator_frame; +struct tree_iterator_frame { + tree_iterator_frame *next; + git_tree *tree; + char *start; + unsigned int index; +}; + +typedef struct { + git_iterator base; + git_repository *repo; + tree_iterator_frame *stack; + git_index_entry entry; + git_buf path; + bool path_has_filename; +} tree_iterator; + +static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) +{ + return (ti->stack == NULL) ? NULL : + git_tree_entry_byindex(ti->stack->tree, ti->stack->index); +} + +static char *tree_iterator__current_filename( + tree_iterator *ti, const git_tree_entry *te) +{ + if (!ti->path_has_filename) { + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return NULL; + ti->path_has_filename = true; + } + + return ti->path.ptr; +} + +static void tree_iterator__pop_frame(tree_iterator *ti) +{ + tree_iterator_frame *tf = ti->stack; + ti->stack = tf->next; + if (ti->stack != NULL) /* don't free the initial tree */ + git_tree_free(tf->tree); + git__free(tf); +} + +static int tree_iterator__to_end(tree_iterator *ti) +{ + while (ti->stack && ti->stack->next) + tree_iterator__pop_frame(ti); + + if (ti->stack) + ti->stack->index = git_tree_entrycount(ti->stack->tree); + + return 0; +} + +static int tree_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = tree_iterator__tree_entry(ti); + + if (entry) + *entry = NULL; + + if (te == NULL) + return 0; + + ti->entry.mode = te->attr; + git_oid_cpy(&ti->entry.oid, &te->oid); + + ti->entry.path = tree_iterator__current_filename(ti, te); + if (ti->entry.path == NULL) + return -1; + + if (ti->base.end && git__prefixcmp(ti->entry.path, ti->base.end) > 0) + return tree_iterator__to_end(ti); + + if (entry) + *entry = &ti->entry; + + return 0; +} + +static int tree_iterator__at_end(git_iterator *self) +{ + return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); +} + +static tree_iterator_frame *tree_iterator__alloc_frame( + git_tree *tree, char *start) +{ + tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame)); + if (!tf) + return NULL; + + tf->tree = tree; + + if (start && *start) { + tf->start = start; + tf->index = git_tree__prefix_position(tree, start); + } + + return tf; +} + +static int tree_iterator__expand_tree(tree_iterator *ti) +{ + int error; + git_tree *subtree; + const git_tree_entry *te = tree_iterator__tree_entry(ti); + tree_iterator_frame *tf; + char *relpath; + + while (te != NULL && git_tree_entry__is_tree(te)) { + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return -1; + + /* check that we have not passed the range end */ + if (ti->base.end != NULL && + git__prefixcmp(ti->path.ptr, ti->base.end) > 0) + return tree_iterator__to_end(ti); + + if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0) + return error; + + relpath = NULL; + + /* apply range start to new frame if relevant */ + if (ti->stack->start && + git__prefixcmp(ti->stack->start, te->filename) == 0) + { + size_t namelen = strlen(te->filename); + if (ti->stack->start[namelen] == '/') + relpath = ti->stack->start + namelen + 1; + } + + if ((tf = tree_iterator__alloc_frame(subtree, relpath)) == NULL) + return -1; + + tf->next = ti->stack; + ti->stack = tf; + + te = tree_iterator__tree_entry(ti); + } + + return 0; +} + +static int tree_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + int error = 0; + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = NULL; + + if (entry != NULL) + *entry = NULL; + + if (ti->path_has_filename) { + git_buf_rtruncate_at_char(&ti->path, '/'); + ti->path_has_filename = false; + } + + while (ti->stack != NULL) { + te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); + if (te != NULL) + break; + + tree_iterator__pop_frame(ti); + + git_buf_rtruncate_at_char(&ti->path, '/'); + } + + if (te && git_tree_entry__is_tree(te)) + error = tree_iterator__expand_tree(ti); + + if (!error) + error = tree_iterator__current(self, entry); + + return error; +} + +static int tree_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* pop stack until matches prefix */ + /* seek item in current frame matching prefix */ + /* push stack which matches prefix */ + return -1; +} + +static void tree_iterator__free(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + while (ti->stack != NULL) + tree_iterator__pop_frame(ti); + git_buf_free(&ti->path); +} + +static int tree_iterator__reset(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + + while (ti->stack && ti->stack->next) + tree_iterator__pop_frame(ti); + + if (ti->stack) + ti->stack->index = + git_tree__prefix_position(ti->stack->tree, ti->base.start); + + git_buf_clear(&ti->path); + + return tree_iterator__expand_tree(ti); +} + +int git_iterator_for_tree_range( + git_iterator **iter, + git_repository *repo, + git_tree *tree, + const char *start, + const char *end) +{ + int error; + tree_iterator *ti; + + if (tree == NULL) + return git_iterator_for_nothing(iter); + + ITERATOR_BASE_INIT(ti, tree, TREE); + + ti->repo = repo; + ti->stack = tree_iterator__alloc_frame(tree, ti->base.start); + + if ((error = tree_iterator__expand_tree(ti)) < 0) + git_iterator_free((git_iterator *)ti); + else + *iter = (git_iterator *)ti; + + return error; +} + + +typedef struct { + git_iterator base; + git_index *index; + unsigned int current; +} index_iterator; + +static int index_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + git_index_entry *ie = git_index_get(ii->index, ii->current); + + if (ie != NULL && + ii->base.end != NULL && + git__prefixcmp(ie->path, ii->base.end) > 0) + { + ii->current = git_index_entrycount(ii->index); + ie = NULL; + } + + if (entry) + *entry = ie; + + return 0; +} + +static int index_iterator__at_end(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + return (ii->current >= git_index_entrycount(ii->index)); +} + +static int index_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + + if (ii->current < git_index_entrycount(ii->index)) + ii->current++; + + return index_iterator__current(self, entry); +} + +static int index_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* find last item before prefix */ + return -1; +} + +static int index_iterator__reset(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + ii->current = 0; + return 0; +} + +static void index_iterator__free(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + git_index_free(ii->index); + ii->index = NULL; +} + +int git_iterator_for_index_range( + git_iterator **iter, + git_repository *repo, + const char *start, + const char *end) +{ + int error; + index_iterator *ii; + + ITERATOR_BASE_INIT(ii, index, INDEX); + + if ((error = git_repository_index(&ii->index, repo)) < 0) + git__free(ii); + else { + ii->current = start ? git_index__prefix_position(ii->index, start) : 0; + *iter = (git_iterator *)ii; + } + + return error; +} + + +typedef struct workdir_iterator_frame workdir_iterator_frame; +struct workdir_iterator_frame { + workdir_iterator_frame *next; + git_vector entries; + unsigned int index; + char *start; +}; + +typedef struct { + git_iterator base; + git_repository *repo; + size_t root_len; + workdir_iterator_frame *stack; + git_ignores ignores; + git_index_entry entry; + git_buf path; + int is_ignored; +} workdir_iterator; + +static workdir_iterator_frame *workdir_iterator__alloc_frame(void) +{ + workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); + if (wf == NULL) + return NULL; + if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) { + git__free(wf); + return NULL; + } + return wf; +} + +static void workdir_iterator__free_frame(workdir_iterator_frame *wf) +{ + unsigned int i; + git_path_with_stat *path; + + git_vector_foreach(&wf->entries, i, path) + git__free(path); + git_vector_free(&wf->entries); + git__free(wf); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi); + +static int workdir_iterator__entry_cmp(const void *prefix, const void *item) +{ + const git_path_with_stat *ps = item; + return git__prefixcmp((const char *)prefix, ps->path); +} + +static int workdir_iterator__expand_dir(workdir_iterator *wi) +{ + int error; + workdir_iterator_frame *wf = workdir_iterator__alloc_frame(); + GITERR_CHECK_ALLOC(wf); + + error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries); + if (error < 0 || wf->entries.length == 0) { + workdir_iterator__free_frame(wf); + return GIT_ENOTFOUND; + } + + git_vector_sort(&wf->entries); + + if (!wi->stack) + wf->start = wi->base.start; + else if (wi->stack->start && + git__prefixcmp(wi->stack->start, wi->path.ptr + wi->root_len) == 0) + wf->start = wi->stack->start; + + if (wf->start) + git_vector_bsearch3( + &wf->index, &wf->entries, workdir_iterator__entry_cmp, wf->start); + + wf->next = wi->stack; + wi->stack = wf; + + /* only push new ignores if this is not top level directory */ + if (wi->stack->next != NULL) { + ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/'); + (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); + } + + return workdir_iterator__update_entry(wi); +} + +static int workdir_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)self; + *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; + return 0; +} + +static int workdir_iterator__at_end(git_iterator *self) +{ + return (((workdir_iterator *)self)->entry.path == NULL); +} + +static int workdir_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + int error; + workdir_iterator *wi = (workdir_iterator *)self; + workdir_iterator_frame *wf; + git_path_with_stat *next; + + if (entry != NULL) + *entry = NULL; + + if (wi->entry.path == NULL) + return 0; + + while ((wf = wi->stack) != NULL) { + next = git_vector_get(&wf->entries, ++wf->index); + if (next != NULL) { + if (strcmp(next->path, DOT_GIT "/") == 0) + continue; + /* else found a good entry */ + break; + } + + /* pop workdir directory stack */ + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); + + if (wi->stack == NULL) { + memset(&wi->entry, 0, sizeof(wi->entry)); + return 0; + } + } + + error = workdir_iterator__update_entry(wi); + + if (!error && entry != NULL) + error = workdir_iterator__current(self, entry); + + return error; +} + +static int workdir_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* pop stack until matching prefix */ + /* find prefix item in current frame */ + /* push subdirectories as deep as possible while matching */ + return 0; +} + +static int workdir_iterator__reset(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + while (wi->stack != NULL && wi->stack->next != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); + } + if (wi->stack) + wi->stack->index = 0; + return 0; +} + +static void workdir_iterator__free(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + + while (wi->stack != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + } + + git_ignore__free(&wi->ignores); + git_buf_free(&wi->path); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi) +{ + git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); + + git_buf_truncate(&wi->path, wi->root_len); + memset(&wi->entry, 0, sizeof(wi->entry)); + + if (!ps) + return 0; + + if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0) + return -1; + + if (wi->base.end && + git__prefixcmp(wi->path.ptr + wi->root_len, wi->base.end) > 0) + return 0; + + wi->entry.path = ps->path; + + /* skip over .git directory */ + if (strcmp(ps->path, DOT_GIT "/") == 0) + return workdir_iterator__advance((git_iterator *)wi, NULL); + + /* if there is an error processing the entry, treat as ignored */ + wi->is_ignored = 1; + + git_index__init_entry_from_stat(&ps->st, &wi->entry); + + /* need different mode here to keep directories during iteration */ + wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); + + /* if this is a file type we don't handle, treat as ignored */ + if (wi->entry.mode == 0) + return 0; + + /* okay, we are far enough along to look up real ignore rule */ + if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0) + return 0; /* if error, ignore it and ignore file */ + + /* detect submodules */ + if (S_ISDIR(wi->entry.mode)) { + bool is_submodule = git_path_contains(&wi->path, DOT_GIT); + + /* if there is no .git, still check submodules data */ + if (!is_submodule) { + int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path); + is_submodule = (res == 0); + if (res == GIT_ENOTFOUND) + giterr_clear(); + } + + /* if submodule, mark as GITLINK and remove trailing slash */ + if (is_submodule) { + size_t len = strlen(wi->entry.path); + assert(wi->entry.path[len - 1] == '/'); + wi->entry.path[len - 1] = '\0'; + wi->entry.mode = S_IFGITLINK; + } + } + + return 0; +} + +int git_iterator_for_workdir_range( + git_iterator **iter, + git_repository *repo, + const char *start, + const char *end) +{ + int error; + workdir_iterator *wi; + + assert(iter && repo); + + if (git_repository_is_bare(repo)) { + giterr_set(GITERR_INVALID, + "Cannot scan working directory for bare repo"); + return -1; + } + + ITERATOR_BASE_INIT(wi, workdir, WORKDIR); + + wi->repo = repo; + + if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 || + git_path_to_dir(&wi->path) < 0 || + git_ignore__for_path(repo, "", &wi->ignores) < 0) + { + git__free(wi); + return -1; + } + + wi->root_len = wi->path.size; + + if ((error = workdir_iterator__expand_dir(wi)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + else { + git_iterator_free((git_iterator *)wi); + wi = NULL; + } + } + + *iter = (git_iterator *)wi; + + return error; +} + + +int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry) +{ + *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL : + tree_iterator__tree_entry((tree_iterator *)iter); + return 0; +} + +int git_iterator_current_is_ignored(git_iterator *iter) +{ + return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 : + ((workdir_iterator *)iter)->is_ignored; +} + +int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type == GIT_ITERATOR_WORKDIR && + wi->entry.path && + S_ISDIR(wi->entry.mode) && + !S_ISGITLINK(wi->entry.mode)) + { + if (workdir_iterator__expand_dir(wi) < 0) + /* if error loading or if empty, skip the directory. */ + return workdir_iterator__advance(iter, entry); + } + + return entry ? git_iterator_current(iter, entry) : 0; +} + +int git_iterator_cmp( + git_iterator *iter, const char *path_prefix) +{ + const git_index_entry *entry; + + /* a "done" iterator is after every prefix */ + if (git_iterator_current(iter, &entry) < 0 || + entry == NULL) + return 1; + + /* a NULL prefix is after any valid iterator */ + if (!path_prefix) + return -1; + + return git__prefixcmp(entry->path, path_prefix); +} + diff --git a/src/iterator.h b/src/iterator.h new file mode 100644 index 000000000..b916a9080 --- /dev/null +++ b/src/iterator.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_iterator_h__ +#define INCLUDE_iterator_h__ + +#include "common.h" +#include "git2/index.h" + +typedef struct git_iterator git_iterator; + +typedef enum { + GIT_ITERATOR_EMPTY = 0, + GIT_ITERATOR_TREE = 1, + GIT_ITERATOR_INDEX = 2, + GIT_ITERATOR_WORKDIR = 3 +} git_iterator_type_t; + +struct git_iterator { + git_iterator_type_t type; + char *start; + char *end; + int (*current)(git_iterator *, const git_index_entry **); + int (*at_end)(git_iterator *); + int (*advance)(git_iterator *, const git_index_entry **); + int (*seek)(git_iterator *, const char *prefix); + int (*reset)(git_iterator *); + void (*free)(git_iterator *); +}; + +extern int git_iterator_for_nothing(git_iterator **iter); + +extern int git_iterator_for_tree_range( + git_iterator **iter, git_repository *repo, git_tree *tree, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_tree( + git_iterator **iter, git_repository *repo, git_tree *tree) +{ + return git_iterator_for_tree_range(iter, repo, tree, NULL, NULL); +} + +extern int git_iterator_for_index_range( + git_iterator **iter, git_repository *repo, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_index( + git_iterator **iter, git_repository *repo) +{ + return git_iterator_for_index_range(iter, repo, NULL, NULL); +} + +extern int git_iterator_for_workdir_range( + git_iterator **iter, git_repository *repo, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_workdir( + git_iterator **iter, git_repository *repo) +{ + return git_iterator_for_workdir_range(iter, repo, NULL, NULL); +} + + +/* Entry is not guaranteed to be fully populated. For a tree iterator, + * we will only populate the mode, oid and path, for example. For a workdir + * iterator, we will not populate the oid. + * + * You do not need to free the entry. It is still "owned" by the iterator. + * Once you call `git_iterator_advance`, then content of the old entry is + * no longer guaranteed to be valid. + */ +GIT_INLINE(int) git_iterator_current( + git_iterator *iter, const git_index_entry **entry) +{ + return iter->current(iter, entry); +} + +GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) +{ + return iter->at_end(iter); +} + +GIT_INLINE(int) git_iterator_advance( + git_iterator *iter, const git_index_entry **entry) +{ + return iter->advance(iter, entry); +} + +GIT_INLINE(int) git_iterator_seek( + git_iterator *iter, const char *prefix) +{ + return iter->seek(iter, prefix); +} + +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +{ + return iter->reset(iter); +} + +GIT_INLINE(void) git_iterator_free(git_iterator *iter) +{ + if (iter == NULL) + return; + + iter->free(iter); + + git__free(iter->start); + git__free(iter->end); + + memset(iter, 0, sizeof(*iter)); + + git__free(iter); +} + +GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) +{ + return iter->type; +} + +extern int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry); + +extern int git_iterator_current_is_ignored(git_iterator *iter); + +/** + * Iterate into a workdir directory. + * + * Workdir iterators do not automatically descend into directories (so that + * when comparing two iterator entries you can detect a newly created + * directory in the workdir). As a result, you may get S_ISDIR items from + * a workdir iterator. If you wish to iterate over the contents of the + * directories you encounter, then call this function when you encounter + * a directory. + * + * If there are no files in the directory, this will end up acting like a + * regular advance and will skip past the directory, so you should be + * prepared for that case. + * + * On non-workdir iterators or if not pointing at a directory, this is a + * no-op and will not advance the iterator. + */ +extern int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry); + +extern int git_iterator_cmp( + git_iterator *iter, const char *path_prefix); + +#endif diff --git a/src/khash.h b/src/khash.h new file mode 100644 index 000000000..bd67fe1f7 --- /dev/null +++ b/src/khash.h @@ -0,0 +1,608 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.6" + +#include +#include +#include + +/* compipler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifdef _MSC_VER +#define inline __inline +#endif + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#ifdef KHASH_LINEAR +#define __ac_inc(k, m) 1 +#else +#define __ac_inc(k, m) (((k)>>3 ^ (k)<<3) | 1) & (m) +#endif + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t inc, k, i, last, mask; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + inc = __ac_inc(k, mask); last = i; /* inc==1 for linear probing */ \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + inc) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) return -1; \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) return -1; \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t inc, k, i; \ + k = __hash_func(key); \ + i = k & new_mask; \ + inc = __ac_inc(k, new_mask); \ + while (!__ac_isempty(new_flags, i)) i = (i + inc) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t inc, k, i, site, last, mask = h->n_buckets - 1; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + inc = __ac_inc(k, mask); last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + inc) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/src/map.h b/src/map.h index be569abc8..96d879547 100644 --- a/src/map.h +++ b/src/map.h @@ -1,31 +1,42 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_map_h__ #define INCLUDE_map_h__ #include "common.h" -/* git__mmap() prot values */ -#define GIT_PROT_NONE 0x0 -#define GIT_PROT_READ 0x1 +/* p_mmap() prot values */ +#define GIT_PROT_NONE 0x0 +#define GIT_PROT_READ 0x1 #define GIT_PROT_WRITE 0x2 -#define GIT_PROT_EXEC 0x4 +#define GIT_PROT_EXEC 0x4 /* git__mmmap() flags values */ -#define GIT_MAP_FILE 0 -#define GIT_MAP_SHARED 1 +#define GIT_MAP_FILE 0 +#define GIT_MAP_SHARED 1 #define GIT_MAP_PRIVATE 2 -#define GIT_MAP_TYPE 0xf -#define GIT_MAP_FIXED 0x10 +#define GIT_MAP_TYPE 0xf +#define GIT_MAP_FIXED 0x10 -typedef struct { /* memory mapped buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ +typedef struct { /* memory mapped buffer */ + void *data; /* data bytes */ + size_t len; /* data length */ #ifdef GIT_WIN32 - HANDLE fmh; /* file mapping handle */ + HANDLE fmh; /* file mapping handle */ #endif } git_map; -extern int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); -extern int git__munmap(git_map *map); +#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ + assert(out != NULL && len > 0); \ + assert((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ + assert((flags & GIT_MAP_FIXED) == 0); } while (0) + +extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); +extern int p_munmap(git_map *map); #endif /* INCLUDE_map_h__ */ diff --git a/src/message.c b/src/message.c new file mode 100644 index 000000000..aa0220fd0 --- /dev/null +++ b/src/message.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "message.h" +#include + +static size_t line_length_without_trailing_spaces(const char *line, size_t len) +{ + while (len) { + unsigned char c = line[len - 1]; + if (!git__isspace(c)) + break; + len--; + } + + return len; +} + +/* Greatly inspired from git.git "stripspace" */ +/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments) +{ + const size_t message_len = strlen(message); + + int consecutive_empty_lines = 0; + size_t i, line_length, rtrimmed_line_length; + char *next_newline; + + for (i = 0; i < strlen(message); i += line_length) { + next_newline = memchr(message + i, '\n', message_len - i); + + if (next_newline != NULL) { + line_length = next_newline - (message + i) + 1; + } else { + line_length = message_len - i; + } + + if (strip_comments && line_length && message[i] == '#') + continue; + + rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); + + if (!rtrimmed_line_length) { + consecutive_empty_lines++; + continue; + } + + if (consecutive_empty_lines > 0 && message_out->size > 0) + git_buf_putc(message_out, '\n'); + + consecutive_empty_lines = 0; + git_buf_put(message_out, message + i, rtrimmed_line_length); + git_buf_putc(message_out, '\n'); + } + + return git_buf_oom(message_out) ? -1 : 0; +} diff --git a/src/message.h b/src/message.h new file mode 100644 index 000000000..ddfa13e18 --- /dev/null +++ b/src/message.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_message_h__ +#define INCLUDE_message_h__ + +#include "buffer.h" + +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments); + +#endif /* INCLUDE_message_h__ */ diff --git a/src/mingw-compat.h b/src/mingw-compat.h deleted file mode 100644 index b7919c2e8..000000000 --- a/src/mingw-compat.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef INCLUDE_mingw_compat__ -#define INCLUDE_mingw_compat__ - -#if defined(__MINGW32__) - -/* use a 64-bit file offset type */ -# define lseek _lseeki64 -# define stat _stati64 -# define fstat _fstati64 - -#endif - -#endif /* INCLUDE_mingw_compat__ */ diff --git a/src/msvc-compat.h b/src/msvc-compat.h deleted file mode 100644 index 1ec85f91b..000000000 --- a/src/msvc-compat.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef INCLUDE_msvc_compat__ -#define INCLUDE_msvc_compat__ - -#if defined(_MSC_VER) - -/* access() mode parameter #defines */ -# define F_OK 0 /* existence check */ -# define W_OK 2 /* write mode check */ -# define R_OK 4 /* read mode check */ - -# define lseek _lseeki64 -# define stat _stat64 -# define fstat _fstat64 - -/* stat: file mode type testing macros */ -# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) - -/* case-insensitive string comparison */ -# define strcasecmp _stricmp -# define strncasecmp _strnicmp - -#if (_MSC_VER >= 1600) -# include -#else -/* add some missing typedef's */ -typedef signed char int8_t; -typedef unsigned char uint8_t; - -typedef short int16_t; -typedef unsigned short uint16_t; - -typedef long int32_t; -typedef unsigned long uint32_t; - -typedef long long int64_t; -typedef unsigned long long uint64_t; - -typedef long long intmax_t; -typedef unsigned long long uintmax_t; -#endif - -#endif - -#endif /* INCLUDE_msvc_compat__ */ diff --git a/src/mwindow.c b/src/mwindow.c new file mode 100644 index 000000000..57adabd48 --- /dev/null +++ b/src/mwindow.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "mwindow.h" +#include "vector.h" +#include "fileops.h" +#include "map.h" +#include "global.h" + +#define DEFAULT_WINDOW_SIZE \ + (sizeof(void*) >= 8 \ + ? 1 * 1024 * 1024 * 1024 \ + : 32 * 1024 * 1024) + +#define DEFAULT_MAPPED_LIMIT \ + ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL)) + +/* + * These are the global options for mmmap limits. + * TODO: allow the user to change these + */ +static struct { + size_t window_size; + size_t mapped_limit; +} _mw_options = { + DEFAULT_WINDOW_SIZE, + DEFAULT_MAPPED_LIMIT, +}; + +/* + * Free all the windows in a sequence, typically because we're done + * with the file + */ +void git_mwindow_free_all(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + unsigned int i; + /* + * Remove these windows from the global list + */ + for (i = 0; i < ctl->windowfiles.length; ++i){ + if (git_vector_get(&ctl->windowfiles, i) == mwf) { + git_vector_remove(&ctl->windowfiles, i); + break; + } + } + + if (ctl->windowfiles.length == 0) { + git_vector_free(&ctl->windowfiles); + ctl->windowfiles.contents = NULL; + } + + while (mwf->windows) { + git_mwindow *w = mwf->windows; + assert(w->inuse_cnt == 0); + + ctl->mapped -= w->window_map.len; + ctl->open_windows--; + + git_futils_mmap_free(&w->window_map); + + mwf->windows = w->next; + git__free(w); + } +} + +/* + * Check if a window 'win' contains the address 'offset' + */ +int git_mwindow_contains(git_mwindow *win, git_off_t offset) +{ + git_off_t win_off = win->offset; + return win_off <= offset + && offset <= (git_off_t)(win_off + win->window_map.len); +} + +/* + * Find the least-recently-used window in a file + */ +void git_mwindow_scan_lru( + git_mwindow_file *mwf, + git_mwindow **lru_w, + git_mwindow **lru_l) +{ + git_mwindow *w, *w_l; + + for (w_l = NULL, w = mwf->windows; w; w = w->next) { + if (!w->inuse_cnt) { + /* + * If the current one is more recent than the last one, + * store it in the output parameter. If lru_w is NULL, + * it's the first loop, so store it as well. + */ + if (!*lru_w || w->last_used < (*lru_w)->last_used) { + *lru_w = w; + *lru_l = w_l; + } + } + w_l = w; + } +} + +/* + * Close the least recently used window. You should check to see if + * the file descriptors need closing from time to time. + */ +static int git_mwindow_close_lru(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + unsigned int i; + git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; + + /* FIXME: Does this give us any advantage? */ + if(mwf->windows) + git_mwindow_scan_lru(mwf, &lru_w, &lru_l); + + for (i = 0; i < ctl->windowfiles.length; ++i) { + git_mwindow *last = lru_w; + git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i); + git_mwindow_scan_lru(cur, &lru_w, &lru_l); + if (lru_w != last) + list = &cur->windows; + } + + if (!lru_w) { + giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU"); + return -1; + } + + ctl->mapped -= lru_w->window_map.len; + git_futils_mmap_free(&lru_w->window_map); + + if (lru_l) + lru_l->next = lru_w->next; + else + *list = lru_w->next; + + git__free(lru_w); + ctl->open_windows--; + + return 0; +} + +static git_mwindow *new_window( + git_mwindow_file *mwf, + git_file fd, + git_off_t size, + git_off_t offset) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + size_t walign = _mw_options.window_size / 2; + git_off_t len; + git_mwindow *w; + + w = git__malloc(sizeof(*w)); + if (w == NULL) + return NULL; + + memset(w, 0x0, sizeof(*w)); + w->offset = (offset / walign) * walign; + + len = size - w->offset; + if (len > (git_off_t)_mw_options.window_size) + len = (git_off_t)_mw_options.window_size; + + ctl->mapped += (size_t)len; + + while (_mw_options.mapped_limit < ctl->mapped && + git_mwindow_close_lru(mwf) == 0) /* nop */; + + /* + * We treat _mw_options.mapped_limit as a soft limit. If we can't find a + * window to close and are above the limit, we still mmap the new + * window. + */ + + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + git__free(w); + return NULL; + } + + ctl->mmap_calls++; + ctl->open_windows++; + + if (ctl->mapped > ctl->peak_mapped) + ctl->peak_mapped = ctl->mapped; + + if (ctl->open_windows > ctl->peak_open_windows) + ctl->peak_open_windows = ctl->open_windows; + + return w; +} + +/* + * Open a new window, closing the least recenty used until we have + * enough space. Don't forget to add it to your list + */ +unsigned char *git_mwindow_open( + git_mwindow_file *mwf, + git_mwindow **cursor, + git_off_t offset, + size_t extra, + unsigned int *left) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow *w = *cursor; + + if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { + if (w) { + w->inuse_cnt--; + } + + for (w = mwf->windows; w; w = w->next) { + if (git_mwindow_contains(w, offset) && + git_mwindow_contains(w, offset + extra)) + break; + } + + /* + * If there isn't a suitable window, we need to create a new + * one. + */ + if (!w) { + w = new_window(mwf, mwf->fd, mwf->size, offset); + if (w == NULL) + return NULL; + w->next = mwf->windows; + mwf->windows = w; + } + } + + /* If we changed w, store it in the cursor */ + if (w != *cursor) { + w->last_used = ctl->used_ctr++; + w->inuse_cnt++; + *cursor = w; + } + + offset -= w->offset; + + if (left) + *left = (unsigned int)(w->window_map.len - offset); + + return (unsigned char *) w->window_map.data + offset; +} + +int git_mwindow_file_register(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + + if (ctl->windowfiles.length == 0 && + git_vector_init(&ctl->windowfiles, 8, NULL) < 0) + return -1; + + return git_vector_insert(&ctl->windowfiles, mwf); +} + +void git_mwindow_close(git_mwindow **window) +{ + git_mwindow *w = *window; + if (w) { + w->inuse_cnt--; + *window = NULL; + } +} diff --git a/src/mwindow.h b/src/mwindow.h new file mode 100644 index 000000000..058027251 --- /dev/null +++ b/src/mwindow.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_mwindow__ +#define INCLUDE_mwindow__ + +#include "map.h" +#include "vector.h" + +typedef struct git_mwindow { + struct git_mwindow *next; + git_map window_map; + git_off_t offset; + size_t last_used; + size_t inuse_cnt; +} git_mwindow; + +typedef struct git_mwindow_file { + git_mwindow *windows; + int fd; + git_off_t size; +} git_mwindow_file; + +typedef struct git_mwindow_ctl { + size_t mapped; + unsigned int open_windows; + unsigned int mmap_calls; + unsigned int peak_open_windows; + size_t peak_mapped; + size_t used_ctr; + git_vector windowfiles; +} git_mwindow_ctl; + +int git_mwindow_contains(git_mwindow *win, git_off_t offset); +void git_mwindow_free_all(git_mwindow_file *mwf); +unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); +void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l); +int git_mwindow_file_register(git_mwindow_file *mwf); +void git_mwindow_close(git_mwindow **w_cursor); + +#endif diff --git a/src/netops.c b/src/netops.c new file mode 100644 index 000000000..e6f3e5627 --- /dev/null +++ b/src/netops.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 _WIN32 +# include +# include +# include +# include +# include +# include +#else +# include +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# endif +#endif + +#ifdef GIT_SSL +# include +# include +#endif + +#include +#include "git2/errors.h" + +#include "common.h" +#include "netops.h" +#include "posix.h" +#include "buffer.h" +#include "transport.h" + +#ifdef GIT_WIN32 +static void net_set_error(const char *str) +{ + int size, error = WSAGetLastError(); + LPSTR err_str = NULL; + + size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + 0, error, 0, (LPSTR)&err_str, 0, 0); + + giterr_set(GITERR_NET, "%s: %s", str, err_str); + LocalFree(err_str); +} +#else +static void net_set_error(const char *str) +{ + giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); +} +#endif + +#ifdef GIT_SSL +static int ssl_set_error(gitno_ssl *ssl, int error) +{ + int err; + err = SSL_get_error(ssl->ssl, error); + giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + return -1; +} +#endif + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) +{ + memset(buf, 0x0, sizeof(gitno_buffer)); + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->fd = t->socket; +#ifdef GIT_SSL + if (t->encrypt) + buf->ssl = &t->ssl; +#endif +} + +#ifdef GIT_SSL +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = SSL_read(ssl->ssl, data, len); + } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) + return ssl_set_error(ssl, ret); + + return ret; +} +#endif + +int gitno_recv(gitno_buffer *buf) +{ + int ret; + +#ifdef GIT_SSL + if (buf->ssl != NULL) { + if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) + return -1; + } else { + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } + } +#else + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } +#endif + + buf->offset += ret; + return ret; +} + +/* Consume up to ptr and move the rest of the buffer to the beginning */ +void gitno_consume(gitno_buffer *buf, const char *ptr) +{ + size_t consumed; + + assert(ptr - buf->data >= 0); + assert(ptr - buf->data <= (int) buf->len); + + consumed = ptr - buf->data; + + memmove(buf->data, ptr, buf->offset - consumed); + memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); + buf->offset -= consumed; +} + +/* Consume const bytes and move the rest of the buffer to the beginning */ +void gitno_consume_n(gitno_buffer *buf, size_t cons) +{ + memmove(buf->data, buf->data + cons, buf->len - buf->offset); + memset(buf->data + cons, 0x0, buf->len - buf->offset); + buf->offset -= cons; +} + +int gitno_ssl_teardown(git_transport *t) +{ +#ifdef GIT_SSL + int ret; +#endif + + if (!t->encrypt) + return 0; + +#ifdef GIT_SSL + + do { + ret = SSL_shutdown(t->ssl.ssl); + } while (ret == 0); + if (ret < 0) + return ssl_set_error(&t->ssl, ret); + + SSL_free(t->ssl.ssl); + SSL_CTX_free(t->ssl.ctx); +#endif + return 0; +} + + +#ifdef GIT_SSL +/* Match host names according to RFC 2818 rules */ +static int match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = tolower(*host); + if (c == h) + return match_host(pattern, host++); + if (h == '.') + return match_host(pattern, host); + host++; + } + return -1; + } + + if (c != tolower(*host++)) + return -1; + } + + return -1; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(git_transport *t, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + int i = -1,j; + + + /* Try to parse the host as an IP address to see if it is */ + if (inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(t->ssl.ssl); + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto on_error; + + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail; + + OPENSSL_free(peer_cn); + + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(&t->ssl, 0); + +cert_fail: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; +} + +static int ssl_setup(git_transport *t, const char *host) +{ + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + t->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (t->ssl.ctx == NULL) + return ssl_set_error(&t->ssl, 0); + + SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL); + if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) + return ssl_set_error(&t->ssl, 0); + + t->ssl.ssl = SSL_new(t->ssl.ctx); + if (t->ssl.ssl == NULL) + return ssl_set_error(&t->ssl, 0); + + if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) + return ssl_set_error(&t->ssl, ret); + + if ((ret = SSL_connect(t->ssl.ssl)) <= 0) + return ssl_set_error(&t->ssl, ret); + + if (t->check_cert && verify_server_cert(t, host) < 0) + return -1; + + return 0; +} +#else +static int ssl_setup(git_transport *t, const char *host) +{ + GIT_UNUSED(t); + GIT_UNUSED(host); + return 0; +} +#endif + +int gitno_connect(git_transport *t, const char *host, const char *port) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + int ret; + GIT_SOCKET s = INVALID_SOCKET; + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) { + giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret)); + return -1; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (s == INVALID_SOCKET) { + net_set_error("error creating socket"); + break; + } + + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; + + /* If we can't connect, try the next one */ + gitno_close(s); + s = INVALID_SOCKET; + } + + /* Oops, we couldn't connect to any address */ + if (s == INVALID_SOCKET && p == NULL) { + giterr_set(GITERR_OS, "Failed to connect to %s", host); + return -1; + } + + t->socket = s; + freeaddrinfo(info); + + if (t->encrypt && ssl_setup(t, host) < 0) + return -1; + + return 0; +} + +#ifdef GIT_SSL +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) +{ + int ret; + size_t off = 0; + + while (off < len) { + ret = SSL_write(ssl->ssl, msg + off, len - off); + if (ret <= 0) + return ssl_set_error(ssl, ret); + + off += ret; + } + + return off; +} +#endif + +int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +{ + int ret; + size_t off = 0; + +#ifdef GIT_SSL + if (t->encrypt) + return send_ssl(&t->ssl, msg, len); +#endif + + while (off < len) { + errno = 0; + ret = p_send(t->socket, msg + off, len - off, flags); + if (ret < 0) { + net_set_error("Error sending data"); + return -1; + } + + off += ret; + } + + return (int)off; +} + + +#ifdef GIT_WIN32 +int gitno_close(GIT_SOCKET s) +{ + return closesocket(s) == SOCKET_ERROR ? -1 : 0; +} +#else +int gitno_close(GIT_SOCKET s) +{ + return close(s); +} +#endif + +int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) +{ + fd_set fds; + struct timeval tv; + + tv.tv_sec = sec; + tv.tv_usec = usec; + + FD_ZERO(&fds); + FD_SET(buf->fd, &fds); + + /* The select(2) interface is silly */ + return select((int)buf->fd + 1, &fds, NULL, NULL, &tv); +} + +int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) +{ + char *colon, *slash, *delim; + + colon = strchr(url, ':'); + slash = strchr(url, '/'); + + if (slash == NULL) { + giterr_set(GITERR_NET, "Malformed URL: missing /"); + return -1; + } + + if (colon == NULL) { + *port = git__strdup(default_port); + } else { + *port = git__strndup(colon + 1, slash - colon - 1); + } + GITERR_CHECK_ALLOC(*port); + + delim = colon == NULL ? slash : colon; + *host = git__strndup(url, delim - url); + GITERR_CHECK_ALLOC(*host); + + return 0; +} diff --git a/src/netops.h b/src/netops.h new file mode 100644 index 000000000..4976f87f8 --- /dev/null +++ b/src/netops.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_netops_h__ +#define INCLUDE_netops_h__ + +#include "posix.h" +#include "transport.h" +#include "common.h" + +typedef struct gitno_buffer { + char *data; + size_t len; + size_t offset; + GIT_SOCKET fd; +#ifdef GIT_SSL + struct gitno_ssl *ssl; +#endif +} gitno_buffer; + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len); +int gitno_recv(gitno_buffer *buf); + +void gitno_consume(gitno_buffer *buf, const char *ptr); +void gitno_consume_n(gitno_buffer *buf, size_t cons); + +int gitno_connect(git_transport *t, const char *host, const char *port); +int gitno_send(git_transport *t, const char *msg, size_t len, int flags); +int gitno_close(GIT_SOCKET s); +int gitno_ssl_teardown(git_transport *t); +int gitno_send_chunk_size(int s, size_t len); +int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); + +int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port); + +#endif diff --git a/src/notes.c b/src/notes.c new file mode 100644 index 000000000..84ad94087 --- /dev/null +++ b/src/notes.c @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "notes.h" + +#include "git2.h" +#include "refs.h" +#include "config.h" +#include "iterator.h" + +static int find_subtree(git_tree **subtree, const git_oid *root, + git_repository *repo, const char *target, int *fanout) +{ + int error; + unsigned int i; + git_tree *tree; + const git_tree_entry *entry; + + *subtree = NULL; + + error = git_tree_lookup(&tree, repo, root); + if (error < 0) + return error; + + for (i=0; ioid, &oid); + note->message = git__strdup(git_blob_rawcontent(blob)); + GITERR_CHECK_ALLOC(note->message); + + *out = note; + + git_blob_free(blob); + return error; +} + +static int note_remove(git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *tree_sha, + const char *target, int nparents, git_commit **parents) +{ + int error, fanout = 0; + git_oid oid; + git_tree *tree; + git_treebuilder *tb; + + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < 0) + return error; + + error = find_blob(&oid, tree, target + fanout); + if (!error) + error = git_treebuilder_create(&tb, tree); + + git_tree_free(tree); + if (error < 0) + return error; + + error = git_treebuilder_remove(tb, target + fanout); + if (!error) + error = git_treebuilder_write(&oid, repo, tb); + + git_treebuilder_free(tb); + if (error < 0) + return error; + + /* create new notes commit */ + + error = git_tree_lookup(&tree, repo, &oid); + if (error < 0) + return error; + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_RM, + tree, nparents, (const git_commit **) parents); + + git_tree_free(tree); + + return error; +} + +static int note_get_default_ref(const char **out, git_repository *repo) +{ + int ret; + git_config *cfg; + + *out = NULL; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + ret = git_config_get_string(out, cfg, "core.notesRef"); + if (ret == GIT_ENOTFOUND) { + *out = GIT_NOTES_DEFAULT_REF; + return 0; + } + + return ret; +} + +static int normalize_namespace(const char **notes_ref, git_repository *repo) +{ + if (*notes_ref) + return 0; + + return note_get_default_ref(notes_ref, repo); +} + +static int retrieve_note_tree_oid(git_oid *tree_oid_out, git_repository *repo, const char *notes_ref) +{ + int error = -1; + git_commit *commit = NULL; + git_oid oid; + + if ((error = git_reference_name_to_oid(&oid, repo, notes_ref)) < 0) + goto cleanup; + + if (git_commit_lookup(&commit, repo, &oid) < 0) + goto cleanup; + + git_oid_cpy(tree_oid_out, git_commit_tree_oid(commit)); + + error = 0; + +cleanup: + git_commit_free(commit); + return error; +} + +int git_note_read(git_note **out, git_repository *repo, + const char *notes_ref, const git_oid *oid) +{ + int error; + char *target; + git_oid sha; + + *out = NULL; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + if ((error = retrieve_note_tree_oid(&sha, repo, notes_ref)) < 0) + return error; + + target = git_oid_allocfmt(oid); + GITERR_CHECK_ALLOC(target); + + error = note_lookup(out, repo, &sha, target); + + git__free(target); + return error; +} + +int git_note_create( + git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *oid, + const char *note) +{ + int error, nparents = 0; + char *target; + git_oid sha; + git_commit *commit = NULL; + git_reference *ref; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + assert(git_reference_type(ref) == GIT_REF_OID); + + /* lookup existing notes tree oid */ + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < 0) + return error; + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + nparents++; + } + + target = git_oid_allocfmt(oid); + GITERR_CHECK_ALLOC(target); + + error = note_write(out, repo, author, committer, notes_ref, + note, nparents ? &sha : NULL, target, + nparents, &commit); + + git__free(target); + git_commit_free(commit); + return error; +} + +int git_note_remove(git_repository *repo, const char *notes_ref, + git_signature *author, git_signature *committer, + const git_oid *oid) +{ + int error; + char *target; + git_oid sha; + git_commit *commit; + git_reference *ref; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < 0) + return error; + + assert(git_reference_type(ref) == GIT_REF_OID); + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < 0) + return error; + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + + target = git_oid_allocfmt(oid); + GITERR_CHECK_ALLOC(target); + + error = note_remove(repo, author, committer, notes_ref, + &sha, target, 1, &commit); + + git__free(target); + git_commit_free(commit); + return error; +} + +int git_note_default_ref(const char **out, git_repository *repo) +{ + assert(repo); + return note_get_default_ref(out, repo); +} + +const char * git_note_message(git_note *note) +{ + assert(note); + return note->message; +} + +const git_oid * git_note_oid(git_note *note) +{ + assert(note); + return ¬e->oid; +} + +void git_note_free(git_note *note) +{ + if (note == NULL) + return; + + git__free(note->message); + git__free(note); +} + +static int process_entry_path( + const char* entry_path, + const git_oid *note_oid, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload) +{ + int i = 0, j = 0, error = -1, len; + git_buf buf = GIT_BUF_INIT; + git_note_data note_data; + + if (git_buf_puts(&buf, entry_path) < 0) + goto cleanup; + + len = git_buf_len(&buf); + + while (i < len) { + if (buf.ptr[i] == '/') { + i++; + continue; + } + + if (git__fromhex(buf.ptr[i]) < 0) { + /* This is not a note entry */ + error = 0; + goto cleanup; + } + + if (i != j) + buf.ptr[j] = buf.ptr[i]; + + i++; + j++; + } + + buf.ptr[j] = '\0'; + buf.size = j; + + if (j != GIT_OID_HEXSZ) { + /* This is not a note entry */ + error = 0; + goto cleanup; + } + + if (git_oid_fromstr(¬e_data.annotated_object_oid, buf.ptr) < 0) + return -1; + + git_oid_cpy(¬e_data.blob_oid, note_oid); + + error = note_cb(¬e_data, payload); + +cleanup: + git_buf_free(&buf); + return error; +} + +int git_note_foreach( + git_repository *repo, + const char *notes_ref, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload) +{ + int error = -1; + git_oid tree_oid; + git_iterator *iter = NULL; + git_tree *tree = NULL; + const git_index_entry *item; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + if ((error = retrieve_note_tree_oid(&tree_oid, repo, notes_ref)) < 0) + goto cleanup; + + if (git_tree_lookup(&tree, repo, &tree_oid) < 0) + goto cleanup; + + if (git_iterator_for_tree(&iter, repo, tree) < 0) + goto cleanup; + + if (git_iterator_current(iter, &item) < 0) + goto cleanup; + + while (item) { + if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0) + goto cleanup; + + if (git_iterator_advance(iter, &item) < 0) + goto cleanup; + } + + error = 0; + +cleanup: + git_iterator_free(iter); + git_tree_free(tree); + return error; +} diff --git a/src/notes.h b/src/notes.h new file mode 100644 index 000000000..219db1ab0 --- /dev/null +++ b/src/notes.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_note_h__ +#define INCLUDE_note_h__ + +#include "common.h" + +#include "git2/oid.h" + +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" + +#define GIT_NOTES_DEFAULT_MSG_ADD \ + "Notes added by 'git_note_create' from libgit2" + +#define GIT_NOTES_DEFAULT_MSG_RM \ + "Notes removed by 'git_note_remove' from libgit2" + +struct git_note { + git_oid oid; + + char *message; +}; + +#endif /* INCLUDE_notes_h__ */ diff --git a/src/object.c b/src/object.c index d14ca8566..d3673eda0 100644 --- a/src/object.c +++ b/src/object.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 @@ -37,8 +19,8 @@ static const int OBJECT_BASE_SIZE = 4096; static struct { - const char *str; /* type name string */ - int loose; /* valid loose object type flag */ + const char *str; /* type name string */ + int loose; /* valid loose object type flag */ size_t size; /* size in bytes of the object structure */ } git_objects_table[] = { /* 0 = GIT_OBJ__EXT1 */ @@ -80,47 +62,59 @@ static int create_object(git_object **object_out, git_otype type) case GIT_OBJ_BLOB: case GIT_OBJ_TREE: object = git__malloc(git_object__size(type)); - if (object == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(object); memset(object, 0x0, git_object__size(type)); break; default: - return git__throw(GIT_EINVALIDTYPE, "The given type is invalid"); + giterr_set(GITERR_INVALID, "The given type is invalid"); + return -1; } object->type = type; *object_out = object; - return GIT_SUCCESS; + return 0; } -int git_object_lookup_prefix(git_object **object_out, git_repository *repo, const git_oid *id, unsigned int len, git_otype type) +int git_object_lookup_prefix( + git_object **object_out, + git_repository *repo, + const git_oid *id, + unsigned int len, + git_otype type) { git_object *object = NULL; + git_odb *odb = NULL; git_odb_object *odb_obj; - int error = GIT_SUCCESS; + int error = 0; assert(repo && object_out && id); if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, - "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + return GIT_EAMBIGUOUS; + + error = git_repository_odb__weakptr(&odb, repo); + if (error < 0) + return error; if (len > GIT_OID_HEXSZ) len = GIT_OID_HEXSZ; - if (len == GIT_OID_HEXSZ) { + if (len == GIT_OID_HEXSZ) { /* We want to match the full id : we can first look up in the cache, * since there is no need to check for non ambiguousity */ object = git_cache_get(&repo->objects, id); if (object != NULL) { - if (type != GIT_OBJ_ANY && type != object->type) - return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); + if (type != GIT_OBJ_ANY && type != object->type) { + git_object_free(object); + giterr_set(GITERR_ODB, "The given type does not match the type in ODB"); + return GIT_ENOTFOUND; + } *object_out = object; - return GIT_SUCCESS; + return 0; } /* Object was not found in the cache, let's explore the backends. @@ -128,7 +122,7 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons * it is the same cost for packed and loose object backends, * but it may be much more costly for sqlite and hiredis. */ - error = git_odb_read(&odb_obj, repo->db, id); + error = git_odb_read(&odb_obj, odb, id); } else { git_oid short_oid; @@ -141,28 +135,29 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have * 2 options : * - We always search in the cache first. If we find that short oid is - * ambiguous, we can stop. But in all the other cases, we must then - * explore all the backends (to find an object if there was match, - * or to check that oid is not ambiguous if we have found 1 match in - * the cache) + * ambiguous, we can stop. But in all the other cases, we must then + * explore all the backends (to find an object if there was match, + * or to check that oid is not ambiguous if we have found 1 match in + * the cache) * - We never explore the cache, go right to exploring the backends * We chose the latter : we explore directly the backends. */ - error = git_odb_read_prefix(&odb_obj, repo->db, &short_oid, len); + error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup object"); + if (error < 0) + return error; if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { - git_odb_object_close(odb_obj); - return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); + git_odb_object_free(odb_obj); + giterr_set(GITERR_ODB, "The given type does not match the type on the ODB"); + return GIT_ENOTFOUND; } type = odb_obj->raw.type; - if ((error = create_object(&object, type)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup object"); + if (create_object(&object, type) < 0) + return -1; /* Initialize parent object */ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); @@ -189,15 +184,15 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons break; } - git_odb_object_close(odb_obj); + git_odb_object_free(odb_obj); - if (error < GIT_SUCCESS) { + if (error < 0) { git_object__free(object); - return git__rethrow(error, "Failed to lookup object"); + return -1; } *object_out = git_cache_try_store(&repo->objects, object); - return GIT_SUCCESS; + return 0; } int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) { @@ -228,12 +223,12 @@ void git_object__free(void *_obj) break; default: - free(object); + git__free(object); break; } } -void git_object_close(git_object *object) +void git_object_free(git_object *object) { if (object == NULL) return; @@ -297,3 +292,42 @@ size_t git_object__size(git_otype type) return git_objects_table[type].size; } +int git_object__resolve_to_type(git_object **obj, git_otype type) +{ + int error = 0; + git_object *scan, *next; + + if (type == GIT_OBJ_ANY) + return 0; + + scan = *obj; + + while (!error && scan && git_object_type(scan) != type) { + + switch (git_object_type(scan)) { + case GIT_OBJ_COMMIT: + { + git_tree *tree = NULL; + error = git_commit_tree(&tree, (git_commit *)scan); + next = (git_object *)tree; + break; + } + + case GIT_OBJ_TAG: + error = git_tag_target(&next, (git_tag *)scan); + break; + + default: + giterr_set(GITERR_REFERENCE, "Object does not resolve to type"); + error = -1; + next = NULL; + break; + } + + git_object_free(scan); + scan = next; + } + + *obj = scan; + return error; +} diff --git a/src/odb.c b/src/odb.c index 8953ec658..a6a18f831 100644 --- a/src/odb.c +++ b/src/odb.c @@ -1,30 +1,12 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" -#include "git2/zlib.h" +#include #include "git2/object.h" #include "fileops.h" #include "hash.h" @@ -46,45 +28,37 @@ typedef struct int is_alternate; } backend_internal; -static int format_object_header(char *hdr, size_t n, git_rawobj *obj) +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! */ - - if (len < 0 || ((size_t) len) >= n) - return git__throw(GIT_ERROR, "Cannot format object header. Length is out of bounds"); + const char *type_str = git_object_type2string(obj_type); + int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); + assert(len > 0 && len <= (int)n); return len+1; } -int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj) +int git_odb__hashobj(git_oid *id, git_rawobj *obj) { git_buf_vec vec[2]; - int hdrlen; + char header[64]; + int hdrlen; - assert(id && hdr && len && obj); + assert(id && obj); if (!git_object_typeisloose(obj->type)) - return git__throw(GIT_ERROR, "Failed to hash object. Wrong object type"); - + return -1; if (!obj->data && obj->len != 0) - return git__throw(GIT_ERROR, "Failed to hash object. No data given"); + return -1; - if ((hdrlen = format_object_header(hdr, n, obj)) < 0) - return git__rethrow(hdrlen, "Failed to hash object"); + hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type); - *len = hdrlen; - - vec[0].data = hdr; - vec[0].len = hdrlen; + vec[0].data = header; + vec[0].len = hdrlen; vec[1].data = obj->data; - vec[1].len = obj->len; + vec[1].len = obj->len; git_hash_vec(id, vec, 2); - return GIT_SUCCESS; + return 0; } @@ -104,8 +78,8 @@ static void free_odb_object(void *o) git_odb_object *object = (git_odb_object *)o; if (object != NULL) { - free(object->raw.data); - free(object); + git__free(object->raw.data); + git__free(object); } } @@ -129,15 +103,104 @@ git_otype git_odb_object_type(git_odb_object *object) return object->raw.type; } -void git_odb_object_close(git_odb_object *object) +void git_odb_object_free(git_odb_object *object) { git_cached_obj_decref((git_cached_obj *)object, &free_odb_object); } +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) +{ + int hdr_len; + char hdr[64], buffer[2048]; + git_hash_ctx *ctx; + + hdr_len = format_object_header(hdr, sizeof(hdr), size, type); + + ctx = git_hash_new_ctx(); + + git_hash_update(ctx, hdr, hdr_len); + + while (size > 0) { + ssize_t read_len = read(fd, buffer, sizeof(buffer)); + + if (read_len < 0) { + git_hash_free_ctx(ctx); + giterr_set(GITERR_OS, "Error reading file"); + return -1; + } + + git_hash_update(ctx, buffer, read_len); + size -= read_len; + } + + git_hash_final(out, ctx); + git_hash_free_ctx(ctx); + + return 0; +} + +int git_odb__hashlink(git_oid *out, const char *path) +{ + struct stat st; + git_off_t size; + int result; + + if (git_path_lstat(path, &st) < 0) + return -1; + + size = st.st_size; + + if (!git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + return -1; + } + + if (S_ISLNK(st.st_mode)) { + char *link_data; + ssize_t read_len; + + link_data = git__malloc((size_t)size); + GITERR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, (size_t)(size + 1)); + if (read_len != (ssize_t)size) { + giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); + return -1; + } + + result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); + git__free(link_data); + } else { + int fd = git_futils_open_ro(path); + if (fd < 0) + return -1; + result = git_odb__hashfd(out, fd, (size_t)size, GIT_OBJ_BLOB); + p_close(fd); + } + + return result; +} + +int git_odb_hashfile(git_oid *out, const char *path, git_otype type) +{ + git_off_t size; + int result, fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + p_close(fd); + return -1; + } + + result = git_odb__hashfd(out, fd, (size_t)size, type); + p_close(fd); + return result; +} + int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type) { - char hdr[64]; - int hdrlen; git_rawobj raw; assert(id); @@ -146,7 +209,7 @@ int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type) raw.len = len; raw.type = type; - return git_odb__hash_obj(id, hdr, sizeof(hdr), &hdrlen, &raw); + return git_odb__hashobj(id, &raw); } /** @@ -171,19 +234,19 @@ static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t fake_wstream *stream = (fake_wstream *)_stream; if (stream->written + len > stream->size) - return GIT_ENOMEM; + return -1; memcpy(stream->buffer + stream->written, data, len); stream->written += len; - return GIT_SUCCESS; + return 0; } static void fake_wstream__free(git_odb_stream *_stream) { fake_wstream *stream = (fake_wstream *)_stream; - free(stream->buffer); - free(stream); + git__free(stream->buffer); + git__free(stream); } static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, size_t size, git_otype type) @@ -191,15 +254,14 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend fake_wstream *stream; stream = git__calloc(1, sizeof(fake_wstream)); - if (stream == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(stream); stream->size = size; stream->type = type; stream->buffer = git__malloc(size); if (stream->buffer == NULL) { - free(stream); - return GIT_ENOMEM; + git__free(stream); + return -1; } stream->stream.backend = backend; @@ -210,7 +272,7 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend stream->stream.mode = GIT_STREAM_WRONLY; *stream_p = (git_odb_stream *)stream; - return GIT_SUCCESS; + return 0; } /*********************************************************** @@ -223,8 +285,8 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend static int backend_sort_cmp(const void *a, const void *b) { - const backend_internal *backend_a = *(const backend_internal **)(a); - const backend_internal *backend_b = *(const backend_internal **)(b); + const backend_internal *backend_a = (const backend_internal *)(a); + const backend_internal *backend_b = (const backend_internal *)(b); if (backend_a->is_alternate == backend_b->is_alternate) return (backend_b->priority - backend_a->priority); @@ -234,25 +296,19 @@ static int backend_sort_cmp(const void *a, const void *b) int git_odb_new(git_odb **out) { - int error; - git_odb *db = git__calloc(1, sizeof(*db)); - if (!db) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(db); - error = git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); - if (error < GIT_SUCCESS) { - free(db); - return git__rethrow(error, "Failed to create object database"); - } - - if ((error = git_vector_init(&db->backends, 4, backend_sort_cmp)) < GIT_SUCCESS) { - free(db); - return git__rethrow(error, "Failed to create object database"); + if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 || + git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) + { + git__free(db); + return -1; } *out = db; - return GIT_SUCCESS; + GIT_REFCOUNT_INC(db); + return 0; } static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int priority, int is_alternate) @@ -261,25 +317,24 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio assert(odb && backend); - if (backend->odb != NULL && backend->odb != odb) - return git__throw(GIT_EBUSY, "The backend is already owned by another ODB"); + /* Check if the backend is already owned by another ODB */ + assert(!backend->odb || backend->odb == odb); internal = git__malloc(sizeof(backend_internal)); - if (internal == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(internal); internal->backend = backend; internal->priority = priority; internal->is_alternate = is_alternate; if (git_vector_insert(&odb->backends, internal) < 0) { - free(internal); - return GIT_ENOMEM; + git__free(internal); + return -1; } git_vector_sort(&odb->backends); internal->backend->odb = odb; - return GIT_SUCCESS; + return 0; } int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) @@ -295,130 +350,125 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates) { git_odb_backend *loose, *packed; - int error; /* add the loose object backend */ - error = git_odb_backend_loose(&loose, objects_dir); - if (error < GIT_SUCCESS) - return error; - - error = add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to add backend"); + if (git_odb_backend_loose(&loose, objects_dir, -1, 0) < 0 || + add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates) < 0) + return -1; /* add the packed file backend */ - error = git_odb_backend_pack(&packed, objects_dir); - if (error < GIT_SUCCESS) - return error; + if (git_odb_backend_pack(&packed, objects_dir) < 0 || + add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0) + return -1; - error = add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to add backend"); - - return GIT_SUCCESS; + return 0; } static int load_alternates(git_odb *odb, const char *objects_dir) { - char alternates_path[GIT_PATH_MAX]; - char *buffer, *alternate; + git_buf alternates_path = GIT_BUF_INIT; + git_buf alternates_buf = GIT_BUF_INIT; + char *buffer; + const char *alternate; + int result = 0; - gitfo_buf alternates_buf = GITFO_BUF_INIT; - int error; + if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) + return -1; - git__joinpath(alternates_path, objects_dir, GIT_ALTERNATES_FILE); + if (git_path_exists(alternates_path.ptr) == false) { + git_buf_free(&alternates_path); + return 0; + } - if (gitfo_exists(alternates_path) < GIT_SUCCESS) - return GIT_SUCCESS; + if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { + git_buf_free(&alternates_path); + return -1; + } - if (gitfo_read_file(&alternates_buf, alternates_path) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to add backend. Can't read alternates"); - - buffer = (char *)alternates_buf.data; - error = GIT_SUCCESS; + buffer = (char *)alternates_buf.ptr; /* add each alternate as a new backend; one alternate per line */ while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { - char full_path[GIT_PATH_MAX]; - if (*alternate == '\0' || *alternate == '#') continue; /* relative path: build based on the current `objects` folder */ if (*alternate == '.') { - git__joinpath(full_path, objects_dir, alternate); - alternate = full_path; + if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0) + break; + alternate = git_buf_cstr(&alternates_path); } - if ((error = add_default_backends(odb, alternate, 1)) < GIT_SUCCESS) + if ((result = add_default_backends(odb, alternate, 1)) < 0) break; } - gitfo_free_buf(&alternates_buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load alternates"); - return error; + git_buf_free(&alternates_path); + git_buf_free(&alternates_buf); + + return result; } int git_odb_open(git_odb **out, const char *objects_dir) { git_odb *db; - int error; assert(out && objects_dir); *out = NULL; - if ((error = git_odb_new(&db)) < 0) - return git__rethrow(error, "Failed to open ODB"); + if (git_odb_new(&db) < 0) + return -1; - if ((error = add_default_backends(db, objects_dir, 0)) < GIT_SUCCESS) - goto cleanup; - - if ((error = load_alternates(db, objects_dir)) < GIT_SUCCESS) - goto cleanup; + if (add_default_backends(db, objects_dir, 0) < 0 || + load_alternates(db, objects_dir) < 0) + { + git_odb_free(db); + return -1; + } *out = db; - return GIT_SUCCESS; - -cleanup: - git_odb_close(db); - return error; /* error already set - pass through */ + return 0; } -void git_odb_close(git_odb *db) +static void odb_free(git_odb *db) { unsigned int i; - if (db == NULL) - return; - for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *backend = internal->backend; if (backend->free) backend->free(backend); - else free(backend); + else git__free(backend); - free(internal); + git__free(internal); } git_vector_free(&db->backends); git_cache_free(&db->cache); - free(db); + git__free(db); +} + +void git_odb_free(git_odb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, odb_free); } int git_odb_exists(git_odb *db, const git_oid *id) { git_odb_object *object; unsigned int i; - int found = 0; + bool found = false; assert(db && id); if ((object = git_cache_get(&db->cache, id)) != NULL) { - git_odb_object_close(object); - return 1; + git_odb_object_free(object); + return (int)true; } for (i = 0; i < db->backends.length && !found; ++i) { @@ -429,7 +479,7 @@ int git_odb_exists(git_odb *db, const git_oid *id) found = b->exists(b, id); } - return found; + return (int)found; } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) @@ -443,8 +493,8 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git if ((object = git_cache_get(&db->cache, id)) != NULL) { *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_close(object); - return GIT_SUCCESS; + git_odb_object_free(object); + return 0; } for (i = 0; i < db->backends.length && error < 0; ++i) { @@ -455,20 +505,20 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git error = b->read_header(len_p, type_p, b, id); } + if (!error || error == GIT_PASSTHROUGH) + return 0; + /* * no backend could read only the header. * try reading the whole object and freeing the contents */ - if (error < 0) { - if ((error = git_odb_read(&object, db, id)) < GIT_SUCCESS) - return error; /* error already set - pass through */ + if ((error = git_odb_read(&object, db, id)) < 0) + return error; /* error already set - pass along */ - *len_p = object->raw.len; - *type_p = object->raw.type; - git_odb_object_close(object); - } - - return GIT_SUCCESS; + *len_p = object->raw.len; + *type_p = object->raw.type; + git_odb_object_free(object); + return 0; } int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) @@ -481,7 +531,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) *out = git_cache_get(&db->cache, id); if (*out != NULL) - return GIT_SUCCESS; + return 0; for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); @@ -491,27 +541,30 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) error = b->read(&raw.data, &raw.len, &raw.type, b, id); } - if (error == GIT_SUCCESS) { - *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw)); - } + /* TODO: If no backends are configured, this returns GIT_ENOTFOUND but + * will never have called giterr_set(). + */ - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to read object"); - return error; + if (error && error != GIT_PASSTHROUGH) + return error; + + *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw)); + return 0; } -int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len) +int git_odb_read_prefix( + git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len) { unsigned int i; int error = GIT_ENOTFOUND; - git_oid full_oid; + git_oid found_full_oid = {{0}}; git_rawobj raw; - int found = 0; + bool found = false; assert(out && db); if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + return git_odb__error_ambiguous("prefix length too short"); if (len > GIT_OID_HEXSZ) len = GIT_OID_HEXSZ; @@ -519,44 +572,42 @@ int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_ if (len == GIT_OID_HEXSZ) { *out = git_cache_get(&db->cache, short_id); if (*out != NULL) - return GIT_SUCCESS; + return 0; } - for (i = 0; i < db->backends.length && found < 2; ++i) { + for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; if (b->read != NULL) { + git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); - switch (error) { - case GIT_SUCCESS: - found++; - break; - case GIT_ENOTFOUND: - break; - case GIT_EAMBIGUOUSOIDPREFIX: - return git__rethrow(error, "Failed to read object. Ambiguous sha1 prefix"); - default: - return git__rethrow(error, "Failed to read object"); - } + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + continue; + + if (error) + return error; + + if (found && git_oid_cmp(&full_oid, &found_full_oid)) + return git_odb__error_ambiguous("multiple matches for prefix"); + found_full_oid = full_oid; + found = true; } } - if (found == 1) { - *out = git_cache_try_store(&db->cache, new_odb_object(&full_oid, &raw)); - } else if (found > 1) { - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read object. Ambiguous sha1 prefix"); - } else { - return git__throw(GIT_ENOTFOUND, "Failed to read object. Object not found"); - } + if (!found) + return git_odb__error_notfound("no match for prefix", short_id); - return GIT_SUCCESS; + *out = git_cache_try_store(&db->cache, new_odb_object(&found_full_oid, &raw)); + return 0; } -int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) +int git_odb_write( + git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) { unsigned int i; int error = GIT_ERROR; + git_odb_stream *stream; assert(oid && db); @@ -572,24 +623,25 @@ int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_o error = b->write(oid, b, data, len, type); } + if (!error || error == GIT_PASSTHROUGH) + return 0; + /* if no backends were able to write the object directly, we try a streaming * write to the backends; just write the whole object into the stream in one * push */ - if (error < GIT_SUCCESS) { - git_odb_stream *stream; - if ((error = git_odb_open_wstream(&stream, db, len, type)) == GIT_SUCCESS) { - stream->write(stream, data, len); - error = stream->finalize_write(oid, stream); - stream->free(stream); - } - } + if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) + return error; - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to write object"); + stream->write(stream, data, len); + error = stream->finalize_write(oid, stream); + stream->free(stream); + + return error; } -int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_otype type) +int git_odb_open_wstream( + git_odb_stream **stream, git_odb *db, size_t size, git_otype type) { unsigned int i; int error = GIT_ERROR; @@ -610,11 +662,13 @@ int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_ error = init_fake_wstream(stream, b, size, type); } - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to open write stream"); + if (error == GIT_PASSTHROUGH) + error = 0; + + return error; } -int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) +int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) { unsigned int i; int error = GIT_ERROR; @@ -629,7 +683,27 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi error = b->readstream(stream, b, oid); } - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to open read stream"); + if (error == GIT_PASSTHROUGH) + error = 0; + + return error; +} + +int git_odb__error_notfound(const char *message, const git_oid *oid) +{ + if (oid != NULL) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), oid); + giterr_set(GITERR_ODB, "Object not found - %s (%s)", message, oid_str); + } else + giterr_set(GITERR_ODB, "Object not found - %s", message); + + return GIT_ENOTFOUND; +} + +int git_odb__error_ambiguous(const char *message) +{ + giterr_set(GITERR_ODB, "Ambiguous SHA1 prefix - %s", message); + return GIT_EAMBIGUOUS; } diff --git a/src/odb.h b/src/odb.h index f3685834e..263e4c30b 100644 --- a/src/odb.h +++ b/src/odb.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_odb_h__ #define INCLUDE_odb_h__ @@ -7,12 +13,17 @@ #include "vector.h" #include "cache.h" +#include "posix.h" + +#define GIT_OBJECTS_DIR "objects/" +#define GIT_OBJECT_DIR_MODE 0777 +#define GIT_OBJECT_FILE_MODE 0444 /* DO NOT EXPORT */ typedef struct { - void *data; /**< Raw, decompressed object data. */ - size_t len; /**< Total number of bytes in data. */ - git_otype type; /**< Type of this object. */ + void *data; /**< Raw, decompressed object data. */ + size_t len; /**< Total number of bytes in data. */ + git_otype type; /**< Type of this object. */ } git_rawobj; /* EXPORT */ @@ -23,11 +34,47 @@ struct git_odb_object { /* EXPORT */ struct git_odb { - void *_internal; + git_refcount rc; git_vector backends; git_cache cache; }; -int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj); +/* + * Hash a git_rawobj internally. + * The `git_rawobj` is supposed to be previously initialized + */ +int git_odb__hashobj(git_oid *id, git_rawobj *obj); + +/* + * Hash an open file descriptor. + * This is a performance call when the contents of a fd need to be hashed, + * but the fd is already open and we have the size of the contents. + * + * Saves us some `stat` calls. + * + * The fd is never closed, not even on error. It must be opened and closed + * by the caller + */ +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink, + * then the raw contents of the symlink will be hashed. Otherwise, this will + * fallback to `git_odb__hashfd`. + * + * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only + * point to blobs. + */ +int git_odb__hashlink(git_oid *out, const char *path); + +/* + * Generate a GIT_ENOTFOUND error for the ODB. + */ +int git_odb__error_notfound(const char *message, const git_oid *oid); + +/* + * Generate a GIT_EAMBIGUOUS error for the ODB. + */ +int git_odb__error_ambiguous(const char *message); #endif diff --git a/src/odb_loose.c b/src/odb_loose.c index deff59ad0..989b03ab2 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -1,50 +1,31 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" -#include "git2/zlib.h" +#include #include "git2/object.h" +#include "git2/oid.h" #include "fileops.h" #include "hash.h" #include "odb.h" -#include "oid.h" #include "delta-apply.h" #include "filebuf.h" #include "git2/odb_backend.h" #include "git2/types.h" -typedef struct { /* object header data */ - git_otype type; /* object type */ - size_t size; /* object size */ +typedef struct { /* object header data */ + git_otype type; /* object type */ + size_t size; /* object size */ } obj_hdr; typedef struct { git_odb_stream stream; git_filebuf fbuf; - int finished; } loose_writestream; typedef struct loose_backend { @@ -69,41 +50,38 @@ typedef struct { } loose_locate_object_state; - /*********************************************************** * * MISCELANEOUS HELPER FUNCTIONS * ***********************************************************/ -static size_t object_file_name(char *name, size_t n, char *dir, const git_oid *id) +static int object_file_name(git_buf *name, const char *dir, const git_oid *id) { - size_t len = strlen(dir); + git_buf_sets(name, dir); - /* check length: 43 = 40 hex sha1 chars + 2 * '/' + '\0' */ - if (len+43 > n) - return len+43; + /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */ + if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0) + return -1; - /* the object dir: eg $GIT_DIR/objects */ - strcpy(name, dir); - if (name[len-1] != '/') - name[len++] = '/'; + git_path_to_dir(name); /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(&name[len], id); - name[len+41] = '\0'; + git_oid_pathfmt(name->ptr + git_buf_len(name), id); + name->size += GIT_OID_HEXSZ + 1; + name->ptr[name->size] = '\0'; return 0; } -static size_t get_binary_object_header(obj_hdr *hdr, gitfo_buf *obj) +static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) { unsigned char c; - unsigned char *data = obj->data; + unsigned char *data = (unsigned char *)obj->ptr; size_t shift, size, used = 0; - if (obj->len == 0) + if (git_buf_len(obj) == 0) return 0; c = data[used++]; @@ -112,7 +90,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, gitfo_buf *obj) size = c & 15; shift = 4; while (c & 0x80) { - if (obj->len <= used) + if (git_buf_len(obj) <= used) return 0; if (sizeof(size_t) * 8 <= shift) return 0; @@ -142,7 +120,7 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data) if (used == 0) return 0; hdr->type = git_object_string2type(typename); - used++; /* consume the space */ + used++; /* consume the space */ /* * length follows immediately in decimal (without @@ -182,29 +160,29 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data) static void init_stream(z_stream *s, void *out, size_t len) { memset(s, 0, sizeof(*s)); - s->next_out = out; - s->avail_out = len; + s->next_out = out; + s->avail_out = (uInt)len; } static void set_stream_input(z_stream *s, void *in, size_t len) { - s->next_in = in; - s->avail_in = len; + s->next_in = in; + s->avail_in = (uInt)len; } static void set_stream_output(z_stream *s, void *out, size_t len) { - s->next_out = out; - s->avail_out = len; + s->next_out = out; + s->avail_out = (uInt)len; } -static int start_inflate(z_stream *s, gitfo_buf *obj, void *out, size_t len) +static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) { int status; init_stream(s, out, len); - set_stream_input(s, obj->data, obj->len); + set_stream_input(s, obj->ptr, git_buf_len(obj)); if ((status = inflateInit(s)) < Z_OK) return status; @@ -221,10 +199,12 @@ static int finish_inflate(z_stream *s) inflateEnd(s); - if ((status != Z_STREAM_END) || (s->avail_in != 0)) - return git__throw(GIT_ERROR, "Failed to finish inflation. Stream aborted prematurely"); + if ((status != Z_STREAM_END) || (s->avail_in != 0)) { + giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely"); + return -1; + } - return GIT_SUCCESS; + return 0; } static int is_zlib_compressed_data(unsigned char *data) @@ -232,7 +212,7 @@ static int is_zlib_compressed_data(unsigned char *data) unsigned int w; w = ((unsigned int)(data[0]) << 8) + data[1]; - return data[0] == 0x78 && !(w % 31); + return (data[0] & 0x8F) == 0x08 && !(w % 31); } static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) @@ -242,27 +222,30 @@ static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) memset(&zs, 0x0, sizeof(zs)); - zs.next_out = out; - zs.avail_out = outlen; + zs.next_out = out; + zs.avail_out = (uInt)outlen; - zs.next_in = in; - zs.avail_in = inlen; + zs.next_in = in; + zs.avail_in = (uInt)inlen; - if (inflateInit(&zs) < Z_OK) - return git__throw(GIT_ERROR, "Failed to inflate buffer"); + if (inflateInit(&zs) < Z_OK) { + giterr_set(GITERR_ZLIB, "Failed to inflate buffer"); + return -1; + } while (status == Z_OK) status = inflate(&zs, Z_FINISH); inflateEnd(&zs); - if ((status != Z_STREAM_END) /*|| (zs.avail_in != 0) */) - return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); + if (status != Z_STREAM_END /* || zs.avail_in != 0 */ || + zs.total_out != outlen) + { + giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely"); + return -1; + } - if (zs.total_out != outlen) - return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); - - return GIT_SUCCESS; + return 0; } static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) @@ -295,7 +278,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) else { set_stream_output(s, buf + used, hdr->size - used); if (finish_inflate(s)) { - free(buf); + git__free(buf); return NULL; } } @@ -309,7 +292,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) * of loose object data into packs. This format is no longer used, but * we must still read it. */ -static int inflate_packlike_loose_disk_obj(git_rawobj *out, gitfo_buf *obj) +static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char *in, *buf; obj_hdr hdr; @@ -319,35 +302,34 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, gitfo_buf *obj) * read the object header, which is an (uncompressed) * binary encoding of the object type and size. */ - if ((used = get_binary_object_header(&hdr, obj)) == 0) - return git__throw(GIT_ERROR, "Failed to inflate loose object. Object has no header"); - - if (!git_object_typeisloose(hdr.type)) - return git__throw(GIT_ERROR, "Failed to inflate loose object. Wrong object type"); + if ((used = get_binary_object_header(&hdr, obj)) == 0 || + !git_object_typeisloose(hdr.type)) { + giterr_set(GITERR_ODB, "Failed to inflate loose object."); + return -1; + } /* * allocate a buffer and inflate the data into it */ buf = git__malloc(hdr.size + 1); - if (!buf) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(buf); - in = ((unsigned char *)obj->data) + used; - len = obj->len - used; - if (inflate_buffer(in, len, buf, hdr.size)) { - free(buf); - return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer"); + in = ((unsigned char *)obj->ptr) + used; + len = obj->size - used; + if (inflate_buffer(in, len, buf, hdr.size) < 0) { + git__free(buf); + return -1; } buf[hdr.size] = '\0'; out->data = buf; - out->len = hdr.size; + out->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } -static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) +static int inflate_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char head[64], *buf; z_stream zs; @@ -357,35 +339,34 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) /* * check for a pack-like loose object */ - if (!is_zlib_compressed_data(obj->data)) + if (!is_zlib_compressed_data((unsigned char *)obj->ptr)) return inflate_packlike_loose_disk_obj(out, obj); /* * inflate the initial part of the io buffer in order * to parse the object header (type and size). */ - if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Could not inflate buffer"); - - if ((used = get_object_header(&hdr, head)) == 0) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Object has no header"); - - if (!git_object_typeisloose(hdr.type)) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Wrong object type"); + if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK || + (used = get_object_header(&hdr, head)) == 0 || + !git_object_typeisloose(hdr.type)) + { + giterr_set(GITERR_ODB, "Failed to inflate disk object."); + return -1; + } /* * allocate a buffer and inflate the object data into it * (including the initial sequence in the head buffer). */ if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL) - return GIT_ENOMEM; + return -1; buf[hdr.size] = '\0'; out->data = buf; - out->len = hdr.size; + out->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } @@ -402,29 +383,31 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) * ***********************************************************/ -static int read_loose(git_rawobj *out, const char *loc) +static int read_loose(git_rawobj *out, git_buf *loc) { int error; - gitfo_buf obj = GITFO_BUF_INIT; + git_buf obj = GIT_BUF_INIT; assert(out && loc); + if (git_buf_oom(loc)) + return -1; + out->data = NULL; - out->len = 0; + out->len = 0; out->type = GIT_OBJ_BAD; - if (gitfo_read_file(&obj, loc) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose object. File not found"); + if (!(error = git_futils_readbuffer(&obj, loc->ptr))) + error = inflate_disk_obj(out, &obj); - error = inflate_disk_obj(out, &obj); - gitfo_free_buf(&obj); + git_buf_free(&obj); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read loose object"); + return error; } -static int read_header_loose(git_rawobj *out, const char *loc) +static int read_header_loose(git_rawobj *out, git_buf *loc) { - int error = GIT_SUCCESS, z_return = Z_ERRNO, read_bytes; + int error = 0, z_return = Z_ERRNO, read_bytes; git_file fd; z_stream zs; obj_hdr header_obj; @@ -432,129 +415,151 @@ static int read_header_loose(git_rawobj *out, const char *loc) assert(out && loc); + if (git_buf_oom(loc)) + return -1; + out->data = NULL; - if ((fd = gitfo_open(loc, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose object header. File not found"); + if ((fd = git_futils_open_ro(loc->ptr)) < 0) + return fd; init_stream(&zs, inflated_buffer, sizeof(inflated_buffer)); - if (inflateInit(&zs) < Z_OK) { - error = GIT_EZLIB; - goto cleanup; - } + z_return = inflateInit(&zs); - do { - if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { + while (z_return == Z_OK) { + if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { set_stream_input(&zs, raw_buffer, read_bytes); z_return = inflate(&zs, 0); - } else { + } else z_return = Z_STREAM_END; - break; - } - } while (z_return == Z_OK); + } if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR) || get_object_header(&header_obj, inflated_buffer) == 0 - || git_object_typeisloose(header_obj.type) == 0) { - error = GIT_EOBJCORRUPTED; - goto cleanup; + || git_object_typeisloose(header_obj.type) == 0) + { + giterr_set(GITERR_ZLIB, "Failed to read loose object header"); + error = -1; + } else { + out->len = header_obj.size; + out->type = header_obj.type; } - out->len = header_obj.size; - out->type = header_obj.type; - -cleanup: finish_inflate(&zs); - gitfo_close(fd); + p_close(fd); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to read loose object header. Header is corrupted"); - - return GIT_SUCCESS; + return error; } -static int locate_object(char *object_location, loose_backend *backend, const git_oid *oid) +static int locate_object( + git_buf *object_location, + loose_backend *backend, + const git_oid *oid) { - object_file_name(object_location, GIT_PATH_MAX, backend->objects_dir, oid); - return gitfo_exists(object_location); + int error = object_file_name(object_location, backend->objects_dir, oid); + + if (!error && !git_path_exists(object_location->ptr)) + return GIT_ENOTFOUND; + + return error; } /* Explore an entry of a directory and see if it matches a short oid */ -int fn_locate_object_short_oid(void *state, char *pathbuf) { +static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { loose_locate_object_state *sstate = (loose_locate_object_state *)state; - size_t pathbuf_len = strlen(pathbuf); - if (pathbuf_len - sstate->dir_len != GIT_OID_HEXSZ - 2) { + if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { /* Entry cannot be an object. Continue to next entry */ - return GIT_SUCCESS; + return 0; } - if (!gitfo_exists(pathbuf) && gitfo_isdir(pathbuf)) { - /* We are already in the directory matching the 2 first hex characters */ - if (!git_oid_match_hex(sstate->short_oid_len-2, sstate->short_oid+2, (unsigned char *)pathbuf + sstate->dir_len)) { + if (git_path_isdir(pathbuf->ptr) == false) { + /* We are already in the directory matching the 2 first hex characters, + * compare the first ncmp characters of the oids */ + if (!memcmp(sstate->short_oid + 2, + (unsigned char *)pathbuf->ptr + sstate->dir_len, + sstate->short_oid_len - 2)) { + if (!sstate->found) { sstate->res_oid[0] = sstate->short_oid[0]; sstate->res_oid[1] = sstate->short_oid[1]; - memcpy(sstate->res_oid+2, pathbuf+sstate->dir_len, GIT_OID_HEXSZ-2); + memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); } sstate->found++; } } - if (sstate->found > 1) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Ambiguous sha1 prefix within loose objects"); - return GIT_SUCCESS; + if (sstate->found > 1) + return git_odb__error_ambiguous("multiple matches in loose objects"); + + return 0; } /* Locate an object matching a given short oid */ -static int locate_object_short_oid(char *object_location, git_oid *res_oid, loose_backend *backend, const git_oid *short_oid, unsigned int len) +static int locate_object_short_oid( + git_buf *object_location, + git_oid *res_oid, + loose_backend *backend, + const git_oid *short_oid, + unsigned int len) { char *objects_dir = backend->objects_dir; size_t dir_len = strlen(objects_dir); loose_locate_object_state state; int error; - if (dir_len+43 > GIT_PATH_MAX) - return git__throw(GIT_ERROR, "Failed to locate object from short oid. Object path too long"); + /* prealloc memory for OBJ_DIR/xx/ */ + if (git_buf_grow(object_location, dir_len + 5) < 0) + return -1; - strcpy(object_location, objects_dir); + git_buf_sets(object_location, objects_dir); + git_path_to_dir(object_location); - /* Add a separator if not already there */ - if (object_location[dir_len-1] != '/') - object_location[dir_len++] = '/'; + /* save adjusted position at end of dir so it can be restored later */ + dir_len = git_buf_len(object_location); /* Convert raw oid to hex formatted oid */ git_oid_fmt((char *)state.short_oid, short_oid); + /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ - sprintf(object_location+dir_len, "%.2s/", state.short_oid); + if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0) + return -1; /* Check that directory exists */ - if (gitfo_exists(object_location) || gitfo_isdir(object_location)) - return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); + if (git_path_isdir(object_location->ptr) == false) + return git_odb__error_notfound("no matching loose object for prefix", short_oid); - state.dir_len = dir_len+3; + state.dir_len = git_buf_len(object_location); state.short_oid_len = len; state.found = 0; + /* Explore directory to find a unique object matching short_oid */ - error = gitfo_dirent(object_location, GIT_PATH_MAX, fn_locate_object_short_oid, &state); - if (error) { - return git__rethrow(error, "Failed to locate object from short oid"); - } - if (!state.found) { - return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); - } + error = git_path_direach( + object_location, fn_locate_object_short_oid, &state); + if (error) + return error; + + if (!state.found) + return git_odb__error_notfound("no matching loose object for prefix", short_oid); /* Convert obtained hex formatted oid to raw */ - error = git_oid_mkstr(res_oid, (char *)state.res_oid); - if (error) { - return git__rethrow(error, "Failed to locate object from short oid"); - } + error = git_oid_fromstr(res_oid, (char *)state.res_oid); + if (error) + return error; /* Update the location according to the oid obtained */ - git_oid_pathfmt(object_location+dir_len, res_oid); - return GIT_SUCCESS; + git_buf_truncate(object_location, dir_len); + if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0) + return -1; + + git_oid_pathfmt(object_location->ptr + dir_len, res_oid); + + object_location->size += GIT_OID_HEXSZ + 1; + object_location->ptr[object_location->size] = '\0'; + + return 0; } @@ -573,9 +578,9 @@ static int locate_object_short_oid(char *object_location, git_oid *res_oid, loos * ***********************************************************/ -int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; int error; @@ -584,39 +589,40 @@ int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend raw.len = 0; raw.type = GIT_OBJ_BAD; - if (locate_object(object_path, (loose_backend *)backend, oid) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose backend header. Object not found"); + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) + error = git_odb__error_notfound("no matching loose object", oid); + else if ((error = read_header_loose(&raw, &object_path)) == 0) { + *len_p = raw.len; + *type_p = raw.type; + } - if ((error = read_header_loose(&raw, object_path)) < GIT_SUCCESS) - return error; + git_buf_free(&object_path); - *len_p = raw.len; - *type_p = raw.type; - return GIT_SUCCESS; + return error; } -int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error; + int error = 0; assert(backend && oid); - if (locate_object(object_path, (loose_backend *)backend, oid) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose backend. Object not found"); + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) + error = git_odb__error_notfound("no matching loose object", oid); + else if ((error = read_loose(&raw, &object_path)) == 0) { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } - if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read loose backend"); + git_buf_free(&object_path); - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - - return GIT_SUCCESS; + return error; } -int loose_backend__read_prefix( +static int loose_backend__read_prefix( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -625,82 +631,90 @@ int loose_backend__read_prefix( const git_oid *short_oid, unsigned int len) { + int error = 0; + if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read loose backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + error = git_odb__error_ambiguous("prefix length too short"); - if (len >= GIT_OID_HEXSZ) { + else if (len >= GIT_OID_HEXSZ) { /* We can fall back to regular read method */ - int error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) + error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) git_oid_cpy(out_oid, short_oid); - - return error; } else { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error; assert(backend && short_oid); - if ((error = locate_object_short_oid(object_path, out_oid, (loose_backend *)backend, short_oid, len)) < 0) { - return git__rethrow(error, "Failed to read loose backend"); + if ((error = locate_object_short_oid(&object_path, out_oid, + (loose_backend *)backend, short_oid, len)) == 0 && + (error = read_loose(&raw, &object_path)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; } - if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read loose backend"); - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; + git_buf_free(&object_path); } - return GIT_SUCCESS; + return error; } -int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) +static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; + int error; assert(backend && oid); - return locate_object(object_path, (loose_backend *)backend, oid) == GIT_SUCCESS; + error = locate_object(&object_path, (loose_backend *)backend, oid); + + git_buf_free(&object_path); + + return !error; } -int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) +static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; loose_backend *backend = (loose_backend *)_stream->backend; + git_buf final_path = GIT_BUF_INIT; + int error = 0; - int error; - char final_path[GIT_PATH_MAX]; + if (git_filebuf_hash(oid, &stream->fbuf) < 0 || + object_file_name(&final_path, backend->objects_dir, oid) < 0 || + git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 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); - if ((error = git_filebuf_hash(oid, &stream->fbuf)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose backend"); + git_buf_free(&final_path); - if (object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid)) - return GIT_ENOMEM; - - if ((error = gitfo_mkdir_2file(final_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose backend"); - - stream->finished = 1; - return git_filebuf_commit_at(&stream->fbuf, final_path); + return error; } -int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) +static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) { loose_writestream *stream = (loose_writestream *)_stream; return git_filebuf_write(&stream->fbuf, data, len); } -void loose_backend__stream_free(git_odb_stream *_stream) +static void loose_backend__stream_free(git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; - if (!stream->finished) - git_filebuf_cleanup(&stream->fbuf); - - free(stream); + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); } static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) @@ -708,22 +722,19 @@ static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype o 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! */ + assert(len > 0); /* otherwise snprintf() is broken */ + assert(((size_t)len) < n); /* otherwise the caller is broken! */ - if (len < 0 || ((size_t) len) >= n) - return git__throw(GIT_ERROR, "Failed to format object header. Length is out of bounds"); return len+1; } -int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) +static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) { loose_backend *backend; - loose_writestream *stream; - - char hdr[64], tmp_path[GIT_PATH_MAX]; - int hdrlen; - int error; + loose_writestream *stream = NULL; + char hdr[64]; + git_buf tmp_path = GIT_BUF_INIT; + int hdrlen; assert(_backend); @@ -731,12 +742,9 @@ int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend *stream_out = NULL; hdrlen = format_object_header(hdr, sizeof(hdr), length, type); - if (hdrlen < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to create loose backend stream. Object is corrupted"); stream = git__calloc(1, sizeof(loose_writestream)); - if (stream == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(stream); stream->stream.backend = _backend; stream->stream.read = NULL; /* read only */ @@ -745,57 +753,94 @@ int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend stream->stream.free = &loose_backend__stream_free; stream->stream.mode = GIT_STREAM_WRONLY; - git__joinpath(tmp_path, backend->objects_dir, "tmp_object"); - - error = git_filebuf_open(&stream->fbuf, tmp_path, - GIT_FILEBUF_HASH_CONTENTS | - GIT_FILEBUF_DEFLATE_CONTENTS | - GIT_FILEBUF_TEMPORARY); - - if (error < GIT_SUCCESS) { - free(stream); - return git__rethrow(error, "Failed to create loose backend stream"); - } - - error = stream->stream.write((git_odb_stream *)stream, hdr, hdrlen); - if (error < GIT_SUCCESS) { + 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 || + stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) + { git_filebuf_cleanup(&stream->fbuf); - free(stream); - return git__rethrow(error, "Failed to create loose backend stream"); + git__free(stream); + stream = NULL; } - + git_buf_free(&tmp_path); *stream_out = (git_odb_stream *)stream; - return GIT_SUCCESS; + + return !stream ? -1 : 0; } -void loose_backend__free(git_odb_backend *_backend) +static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) +{ + int error = 0, header_len; + git_buf final_path = GIT_BUF_INIT; + char header[64]; + git_filebuf fbuf = GIT_FILEBUF_INIT; + loose_backend *backend; + + backend = (loose_backend *)_backend; + + /* prepare the header for the file */ + header_len = 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_HASH_CONTENTS | + GIT_FILEBUF_TEMPORARY | + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0) + { + error = -1; + goto cleanup; + } + + git_filebuf_write(&fbuf, header, header_len); + git_filebuf_write(&fbuf, data, len); + git_filebuf_hash(oid, &fbuf); + + if (object_file_name(&final_path, backend->objects_dir, oid) < 0 || + git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 || + git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0) + error = -1; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&fbuf); + git_buf_free(&final_path); + return error; +} + +static void loose_backend__free(git_odb_backend *_backend) { loose_backend *backend; assert(_backend); backend = (loose_backend *)_backend; - free(backend->objects_dir); - free(backend); + git__free(backend->objects_dir); + git__free(backend); } -int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir) +int git_odb_backend_loose( + git_odb_backend **backend_out, + const char *objects_dir, + int compression_level, + int do_fsync) { loose_backend *backend; backend = git__calloc(1, sizeof(loose_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); backend->objects_dir = git__strdup(objects_dir); - if (backend->objects_dir == NULL) { - free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->objects_dir); - backend->object_zlib_level = Z_BEST_SPEED; - backend->fsync_object_files = 0; + if (compression_level < 0) + compression_level = Z_BEST_SPEED; + + backend->object_zlib_level = compression_level; + backend->fsync_object_files = do_fsync; backend->parent.read = &loose_backend__read; + backend->parent.write = &loose_backend__write; backend->parent.read_prefix = &loose_backend__read_prefix; backend->parent.read_header = &loose_backend__read_header; backend->parent.writestream = &loose_backend__stream; @@ -803,5 +848,5 @@ int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir backend->parent.free = &loose_backend__free; *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; + return 0; } diff --git a/src/odb_pack.c b/src/odb_pack.c index 525cfa429..458f288d9 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -1,142 +1,38 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" -#include "git2/zlib.h" +#include #include "git2/repository.h" +#include "git2/oid.h" #include "fileops.h" #include "hash.h" #include "odb.h" -#include "oid.h" #include "delta-apply.h" #include "sha1_lookup.h" +#include "mwindow.h" +#include "pack.h" #include "git2/odb_backend.h" -#define DEFAULT_WINDOW_SIZE \ - (sizeof(void*) >= 8 \ - ? 1 * 1024 * 1024 * 1024 \ - : 32 * 1024 * 1024) - -#define DEFAULT_MAPPED_LIMIT \ - ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256)) - -#define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 -#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) -struct pack_header { - uint32_t hdr_signature; - uint32_t hdr_version; - uint32_t hdr_entries; -}; - -/* - * The first four bytes of index formats later than version 1 should - * start with this signature, as all older git binaries would find this - * value illegal and abort reading the file. - * - * This is the case because the number of objects in a packfile - * cannot exceed 1,431,660,000 as every object would need at least - * 3 bytes of data and the overall packfile cannot exceed 4 GiB with - * version 1 of the index file due to the offsets limited to 32 bits. - * Clearly the signature exceeds this maximum. - * - * Very old git binaries will also compare the first 4 bytes to the - * next 4 bytes in the index and abort with a "non-monotonic index" - * error if the second 4 byte word is smaller than the first 4 - * byte word. This would be true in the proposed future index - * format as idx_signature would be greater than idx_version. - */ -#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ - -struct pack_idx_header { - uint32_t idx_signature; - uint32_t idx_version; -}; - -struct pack_window { - struct pack_window *next; - git_map window_map; - off_t offset; - unsigned int last_used; - unsigned int inuse_cnt; -}; - -struct pack_file { - struct pack_window *windows; - off_t pack_size; - - git_map index_map; - - uint32_t num_objects; - uint32_t num_bad_objects; - git_oid *bad_object_sha1; /* array of git_oid */ - - int index_version; - git_time_t mtime; - int pack_fd; - unsigned pack_local:1, pack_keep:1; - git_oid sha1; - - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[GIT_FLEX_ARRAY]; /* more */ -}; - -struct pack_entry { - off_t offset; - git_oid sha1; - struct pack_file *p; -}; - struct pack_backend { git_odb_backend parent; git_vector packs; - struct pack_file *last_found; + struct git_pack_file *last_found; char *pack_folder; time_t pack_folder_mtime; - - size_t window_size; /* needs default value */ - - size_t mapped_limit; /* needs default value */ - size_t peak_mapped; - size_t mapped; - - size_t used_ctr; - - unsigned int peak_open_windows; - unsigned int open_windows; - - unsigned int mmap_calls; }; /** * The wonderful tale of a Packed Object lookup query * =================================================== - * A riveting and epic story of epicness and ASCII - * art, presented by yours truly, - * Sir Vicent of Marti + * A riveting and epic story of epicness and ASCII + * art, presented by yours truly, + * Sir Vicent of Marti * * * Chapter 1: Once upon a time... @@ -147,32 +43,32 @@ struct pack_backend { * | Creates the pack backend structure, initializes the * | callback pointers to our default read() and exist() methods, * | and tries to preload all the known packfiles in the ODB. - * | + * | * |-# packfile_load_all - * | Tries to find the `pack` folder, if it exists. ODBs without - * | a pack folder are ignored altogether. If there's a `pack` folder - * | we run a `dirent` callback through every file in the pack folder - * | to find our packfiles. The packfiles are then sorted according - * | to a sorting callback. - * | - * |-# packfile_load__cb - * | | This callback is called from `dirent` with every single file - * | | inside the pack folder. We find the packs by actually locating - * | | their index (ends in ".idx"). From that index, we verify that - * | | the corresponding packfile exists and is valid, and if so, we - * | | add it to the pack list. - * | | - * | |-# packfile_check - * | Make sure that there's a packfile to back this index, and store - * | some very basic information regarding the packfile itself, - * | such as the full path, the size, and the modification time. - * | We don't actually open the packfile to check for internal consistency. - * | - * |-# packfile_sort__cb - * Sort all the preloaded packs according to some specific criteria: - * we prioritize the "newer" packs because it's more likely they - * contain the objects we are looking for, and we prioritize local - * packs over remote ones. + * | Tries to find the `pack` folder, if it exists. ODBs without + * | a pack folder are ignored altogether. If there's a `pack` folder + * | we run a `dirent` callback through every file in the pack folder + * | to find our packfiles. The packfiles are then sorted according + * | to a sorting callback. + * | + * |-# packfile_load__cb + * | | This callback is called from `dirent` with every single file + * | | inside the pack folder. We find the packs by actually locating + * | | their index (ends in ".idx"). From that index, we verify that + * | | the corresponding packfile exists and is valid, and if so, we + * | | add it to the pack list. + * | | + * | |-# packfile_check + * | Make sure that there's a packfile to back this index, and store + * | some very basic information regarding the packfile itself, + * | such as the full path, the size, and the modification time. + * | We don't actually open the packfile to check for internal consistency. + * | + * |-# packfile_sort__cb + * Sort all the preloaded packs according to some specific criteria: + * we prioritize the "newer" packs because it's more likely they + * contain the objects we are looking for, and we prioritize local + * packs over remote ones. * * * @@ -180,41 +76,41 @@ struct pack_backend { * A standard packed `exist` query for an OID * -------------------------------------------------- * - * # pack_backend__exists - * | Check if the given SHA1 oid exists in any of the packs - * | that have been loaded for our ODB. - * | - * |-# pack_entry_find - * | Iterate through all the packs that have been preloaded - * | (starting by the pack where the latest object was found) - * | to try to find the OID in one of them. - * | - * |-# pack_entry_find1 - * | Check the index of an individual pack to see if the SHA1 - * | OID can be found. If we can find the offset to that SHA1 - * | inside of the index, that means the object is contained - * | inside of the packfile and we can stop searching. - * | Before returning, we verify that the packfile behing the - * | index we are searching still exists on disk. - * | - * |-# pack_entry_find_offset - * | | Mmap the actual index file to disk if it hasn't been opened - * | | yet, and run a binary search through it to find the OID. - * | | See for specifics - * | | on the Packfile Index format and how do we find entries in it. - * | | - * | |-# pack_index_open - * | | Guess the name of the index based on the full path to the - * | | packfile, open it and verify its contents. Only if the index - * | | has not been opened already. - * | | - * | |-# pack_index_check - * | Mmap the index file and do a quick run through the header - * | to guess the index version (right now we support v1 and v2), - * | and to verify that the size of the index makes sense. - * | - * |-# packfile_open - * See `packfile_open` in Chapter 3 + * # pack_backend__exists + * | Check if the given SHA1 oid exists in any of the packs + * | that have been loaded for our ODB. + * | + * |-# pack_entry_find + * | Iterate through all the packs that have been preloaded + * | (starting by the pack where the latest object was found) + * | to try to find the OID in one of them. + * | + * |-# pack_entry_find1 + * | Check the index of an individual pack to see if the SHA1 + * | OID can be found. If we can find the offset to that SHA1 + * | inside of the index, that means the object is contained + * | inside of the packfile and we can stop searching. + * | Before returning, we verify that the packfile behing the + * | index we are searching still exists on disk. + * | + * |-# pack_entry_find_offset + * | | Mmap the actual index file to disk if it hasn't been opened + * | | yet, and run a binary search through it to find the OID. + * | | See for specifics + * | | on the Packfile Index format and how do we find entries in it. + * | | + * | |-# pack_index_open + * | | Guess the name of the index based on the full path to the + * | | packfile, open it and verify its contents. Only if the index + * | | has not been opened already. + * | | + * | |-# pack_index_check + * | Mmap the index file and do a quick run through the header + * | to guess the index version (right now we support v1 and v2), + * | and to verify that the size of the index makes sense. + * | + * |-# packfile_open + * See `packfile_open` in Chapter 3 * * * @@ -226,121 +122,34 @@ struct pack_backend { */ - - /*********************************************************** * * FORWARD DECLARATIONS * ***********************************************************/ -static void pack_window_free_all(struct pack_backend *backend, struct pack_file *p); -static int pack_window_contains(struct pack_window *win, off_t offset); - -static void pack_window_scan_lru(struct pack_file *p, struct pack_file **lru_p, - struct pack_window **lru_w, struct pack_window **lru_l); - -static int pack_window_close_lru( struct pack_backend *backend, - struct pack_file *current, git_file keep_fd); - -static void pack_window_close(struct pack_window **w_cursor); - -static unsigned char *pack_window_open( struct pack_backend *backend, - struct pack_file *p, struct pack_window **w_cursor, off_t offset, - unsigned int *left); +static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p); +static int pack_window_contains(git_mwindow *win, off_t offset); static int packfile_sort__cb(const void *a_, const void *b_); -static void pack_index_free(struct pack_file *p); - -static int pack_index_check(const char *path, struct pack_file *p); -static int pack_index_open(struct pack_file *p); - -static struct pack_file *packfile_alloc(int extra); -static int packfile_open(struct pack_file *p); -static int packfile_check(struct pack_file **pack_out, const char *path); -static int packfile_load__cb(void *_data, char *path); +static int packfile_load__cb(void *_data, git_buf *path); static int packfile_refresh_all(struct pack_backend *backend); -static off_t nth_packed_object_offset(const struct pack_file *p, uint32_t n); +static int pack_entry_find(struct git_pack_entry *e, + struct pack_backend *backend, const git_oid *oid); /* Can find the offset of an object given * a prefix of an identifier. - * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid - * is ambiguous within the pack. + * Sets GIT_EAMBIGUOUS if short oid is ambiguous. * This method assumes that len is between * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. */ -static int pack_entry_find_offset( - off_t *offset_out, - git_oid *found_oid, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len); - -static int pack_entry_find1( - struct pack_entry *e, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len); - -static int pack_entry_find(struct pack_entry *e, - struct pack_backend *backend, const git_oid *oid); - -/* Can find the offset of an object given - * a prefix of an identifier. - * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid - * is ambiguous. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_prefix(struct pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - unsigned int len); - -static off_t get_delta_base(struct pack_backend *backend, - struct pack_file *p, struct pack_window **w_curs, - off_t *curpos, git_otype type, - off_t delta_obj_offset); - -static unsigned long packfile_unpack_header1( - size_t *sizep, - git_otype *type, - const unsigned char *buf, - unsigned long len); - -static int packfile_unpack_header( - size_t *size_p, - git_otype *type_p, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos); - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t size, - git_otype type); - -static int packfile_unpack_delta( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t delta_size, - git_otype delta_type, - off_t obj_offset); - -static int packfile_unpack(git_rawobj *obj, struct pack_backend *backend, - struct pack_file *p, off_t obj_offset); - - +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + unsigned int len); @@ -350,357 +159,32 @@ static int packfile_unpack(git_rawobj *obj, struct pack_backend *backend, * ***********************************************************/ -void pack_window_free_all(struct pack_backend *backend, struct pack_file *p) +GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p) { - while (p->windows) { - struct pack_window *w = p->windows; - assert(w->inuse_cnt == 0); - - backend->mapped -= w->window_map.len; - backend->open_windows--; - - gitfo_free_map(&w->window_map); - - p->windows = w->next; - free(w); - } + GIT_UNUSED(backend); + git_mwindow_free_all(&p->mwf); } -GIT_INLINE(int) pack_window_contains(struct pack_window *win, off_t offset) +GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset) { /* We must promise at least 20 bytes (one hash) after the * offset is available from this window, otherwise the offset * is not actually in this window and a different window (which - * has that one hash excess) must be used. This is to support + * has that one hash excess) must be used. This is to support * the object header and delta base parsing routines below. */ - off_t win_off = win->offset; - return win_off <= offset - && (offset + 20) <= (off_t)(win_off + win->window_map.len); + return git_mwindow_contains(win, offset + 20); } -static void pack_window_scan_lru( - struct pack_file *p, - struct pack_file **lru_p, - struct pack_window **lru_w, - struct pack_window **lru_l) -{ - struct pack_window *w, *w_l; - - for (w_l = NULL, w = p->windows; w; w = w->next) { - if (!w->inuse_cnt) { - if (!*lru_w || w->last_used < (*lru_w)->last_used) { - *lru_p = p; - *lru_w = w; - *lru_l = w_l; - } - } - w_l = w; - } -} - -static int pack_window_close_lru( - struct pack_backend *backend, - struct pack_file *current, - git_file keep_fd) -{ - struct pack_file *lru_p = NULL; - struct pack_window *lru_w = NULL, *lru_l = NULL; - size_t i; - - if (current) - pack_window_scan_lru(current, &lru_p, &lru_w, &lru_l); - - for (i = 0; i < backend->packs.length; ++i) - pack_window_scan_lru(git_vector_get(&backend->packs, i), &lru_p, &lru_w, &lru_l); - - if (lru_p) { - backend->mapped -= lru_w->window_map.len; - gitfo_free_map(&lru_w->window_map); - - if (lru_l) - lru_l->next = lru_w->next; - else { - lru_p->windows = lru_w->next; - if (!lru_p->windows && lru_p->pack_fd != keep_fd) { - gitfo_close(lru_p->pack_fd); - lru_p->pack_fd = -1; - } - } - - free(lru_w); - backend->open_windows--; - return GIT_SUCCESS; - } - - return git__throw(GIT_ERROR, "Failed to close pack window"); -} - -static void pack_window_close(struct pack_window **w_cursor) -{ - struct pack_window *w = *w_cursor; - if (w) { - w->inuse_cnt--; - *w_cursor = NULL; - } -} - -static unsigned char *pack_window_open( - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_cursor, - off_t offset, - unsigned int *left) -{ - struct pack_window *win = *w_cursor; - - if (p->pack_fd == -1 && packfile_open(p) < GIT_SUCCESS) - return NULL; - - /* Since packfiles end in a hash of their content and it's - * pointless to ask for an offset into the middle of that - * hash, and the pack_window_contains function above wouldn't match - * don't allow an offset too close to the end of the file. - */ - if (offset > (p->pack_size - 20)) - return NULL; - - if (!win || !pack_window_contains(win, offset)) { - - if (win) - win->inuse_cnt--; - - for (win = p->windows; win; win = win->next) { - if (pack_window_contains(win, offset)) - break; - } - - if (!win) { - size_t window_align = backend->window_size / 2; - size_t len; - - win = git__calloc(1, sizeof(*win)); - win->offset = (offset / window_align) * window_align; - - len = (size_t)(p->pack_size - win->offset); - if (len > backend->window_size) - len = backend->window_size; - - backend->mapped += len; - - while (backend->mapped_limit < backend->mapped && - pack_window_close_lru(backend, p, p->pack_fd) == GIT_SUCCESS) {} - - if (gitfo_map_ro(&win->window_map, p->pack_fd, - win->offset, len) < GIT_SUCCESS) - return NULL; - - backend->mmap_calls++; - backend->open_windows++; - - if (backend->mapped > backend->peak_mapped) - backend->peak_mapped = backend->mapped; - - if (backend->open_windows > backend->peak_open_windows) - backend->peak_open_windows = backend->open_windows; - - win->next = p->windows; - p->windows = win; - } - } - - if (win != *w_cursor) { - win->last_used = backend->used_ctr++; - win->inuse_cnt++; - *w_cursor = win; - } - - offset -= win->offset; - assert(git__is_sizet(offset)); - - if (left) - *left = win->window_map.len - (size_t)offset; - - return (unsigned char *)win->window_map.data + offset; -} - - - - - - - -/*********************************************************** - * - * PACK INDEX METHODS - * - ***********************************************************/ - -static void pack_index_free(struct pack_file *p) -{ - if (p->index_map.data) { - gitfo_free_map(&p->index_map); - p->index_map.data = NULL; - } -} - -static int pack_index_check(const char *path, struct pack_file *p) -{ - struct pack_idx_header *hdr; - uint32_t version, nr, i, *index; - - void *idx_map; - size_t idx_size; - - struct stat st; - - /* TODO: properly open the file without access time */ - git_file fd = gitfo_open(path, O_RDONLY /*| O_NOATIME */); - - int error; - - if (fd < 0) - return git__throw(GIT_EOSERR, "Failed to check index. File missing or corrupted"); - - if (gitfo_fstat(fd, &st) < GIT_SUCCESS) { - gitfo_close(fd); - return git__throw(GIT_EOSERR, "Failed to check index. File appears to be corrupted"); - } - - if (!git__is_sizet(st.st_size)) - return GIT_ENOMEM; - - idx_size = (size_t)st.st_size; - - if (idx_size < 4 * 256 + 20 + 20) { - gitfo_close(fd); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); - } - - error = gitfo_map_ro(&p->index_map, fd, 0, idx_size); - gitfo_close(fd); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to check index"); - - hdr = idx_map = p->index_map.data; - - if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { - version = ntohl(hdr->idx_version); - - if (version < 2 || version > 2) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Unsupported index version"); - } - - } else - version = 1; - - nr = 0; - index = idx_map; - - if (version > 1) - index += 2; /* skip index header */ - - for (i = 0; i < 256; i++) { - uint32_t n = ntohl(index[i]); - if (n < nr) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Index is non-monotonic"); - } - nr = n; - } - - if (version == 1) { - /* - * Total size: - * - 256 index entries 4 bytes each - * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - */ - if (idx_size != 4*256 + nr * 24 + 20 + 20) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); - } - } else if (version == 2) { - /* - * Minimum size: - * - 8 bytes of header - * - 256 index entries 4 bytes each - * - 20-byte sha1 entry * nr - * - 4-byte crc entry * nr - * - 4-byte offset entry * nr - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - * And after the 4-byte offset table might be a - * variable sized table containing 8-byte entries - * for offsets larger than 2^31. - */ - unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; - unsigned long max_size = min_size; - - if (nr) - max_size += (nr - 1)*8; - - if (idx_size < min_size || idx_size > max_size) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Wrong index size"); - } - - /* Make sure that off_t is big enough to access the whole pack... - * Is this an issue in libgit2? It shouldn't. */ - if (idx_size != min_size && (sizeof(off_t) <= 4)) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOSERR, "Failed to check index. off_t not big enough to access the whole pack"); - } - } - - p->index_version = version; - p->num_objects = nr; - return GIT_SUCCESS; -} - -static int pack_index_open(struct pack_file *p) -{ - char *idx_name; - int error; - - if (p->index_map.data) - return GIT_SUCCESS; - - idx_name = git__strdup(p->pack_name); - strcpy(idx_name + strlen(idx_name) - STRLEN(".pack"), ".idx"); - - error = pack_index_check(idx_name, p); - free(idx_name); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to open index"); -} - - - - - - - - - -/*********************************************************** - * - * PACKFILE METHODS - * - ***********************************************************/ - static int packfile_sort__cb(const void *a_, const void *b_) { - struct pack_file *a = *((struct pack_file **)a_); - struct pack_file *b = *((struct pack_file **)b_); + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; int st; /* * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, + * variant of the project than remote ones. In addition, * remote ones could be on a network mounted filesystem. * Favor local ones for these reasons. */ @@ -721,174 +205,32 @@ static int packfile_sort__cb(const void *a_, const void *b_) return -1; } -static struct pack_file *packfile_alloc(int extra) -{ - struct pack_file *p = git__malloc(sizeof(*p) + extra); - memset(p, 0, sizeof(*p)); - p->pack_fd = -1; - return p; -} -static void packfile_free(struct pack_backend *backend, struct pack_file *p) -{ - assert(p); - - /* clear_delta_base_cache(); */ - pack_window_free_all(backend, p); - - if (p->pack_fd != -1) - gitfo_close(p->pack_fd); - - pack_index_free(p); - - free(p->bad_object_sha1); - free(p); -} - -static int packfile_open(struct pack_file *p) -{ - struct stat st; - struct pack_header hdr; - git_oid sha1; - unsigned char *idx_sha1; - - if (!p->index_map.data && pack_index_open(p) < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "Failed to open packfile. File not found"); - - /* TODO: open with noatime */ - p->pack_fd = gitfo_open(p->pack_name, O_RDONLY); - if (p->pack_fd < 0 || gitfo_fstat(p->pack_fd, &st) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to open packfile. File appears to be corrupted"); - - /* If we created the struct before we had the pack we lack size. */ - if (!p->pack_size) { - if (!S_ISREG(st.st_mode)) - goto cleanup; - p->pack_size = (off_t)st.st_size; - } else if (p->pack_size != st.st_size) - goto cleanup; - -#if 0 - /* We leave these file descriptors open with sliding mmap; - * there is no point keeping them open across exec(), though. - */ - fd_flag = fcntl(p->pack_fd, F_GETFD, 0); - if (fd_flag < 0) - return error("cannot determine file descriptor flags"); - - fd_flag |= FD_CLOEXEC; - if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - return GIT_EOSERR; -#endif - - /* Verify we recognize this pack file format. */ - if (gitfo_read(p->pack_fd, &hdr, sizeof(hdr)) < GIT_SUCCESS) - goto cleanup; - - if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) - goto cleanup; - - if (!pack_version_ok(hdr.hdr_version)) - goto cleanup; - - /* Verify the pack matches its index. */ - if (p->num_objects != ntohl(hdr.hdr_entries)) - goto cleanup; - - if (gitfo_lseek(p->pack_fd, p->pack_size - GIT_OID_RAWSZ, SEEK_SET) == -1) - goto cleanup; - - if (gitfo_read(p->pack_fd, sha1.id, GIT_OID_RAWSZ) < GIT_SUCCESS) - goto cleanup; - - idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; - - if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) != 0) - goto cleanup; - - return GIT_SUCCESS; - -cleanup: - gitfo_close(p->pack_fd); - p->pack_fd = -1; - return git__throw(GIT_EPACKCORRUPTED, "Failed to packfile. Pack is corrupted"); -} - -static int packfile_check(struct pack_file **pack_out, const char *path) -{ - struct stat st; - struct pack_file *p; - size_t path_len; - - *pack_out = NULL; - path_len = strlen(path); - p = packfile_alloc(path_len + 2); - - /* - * Make sure a corresponding .pack file exists and that - * the index looks sane. - */ - path_len -= STRLEN(".idx"); - if (path_len < 1) { - free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. Wrong path name"); - } - - memcpy(p->pack_name, path, path_len); - - strcpy(p->pack_name + path_len, ".keep"); - if (gitfo_exists(p->pack_name) == GIT_SUCCESS) - p->pack_keep = 1; - - strcpy(p->pack_name + path_len, ".pack"); - if (gitfo_stat(p->pack_name, &st) < GIT_SUCCESS || !S_ISREG(st.st_mode)) { - free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. File not found"); - } - - /* ok, it looks sane as far as we can check without - * actually mapping the pack file. - */ - p->pack_size = (off_t)st.st_size; - p->pack_local = 1; - p->mtime = (git_time_t)st.st_mtime; - - /* see if we can parse the sha1 oid in the packfile name */ - if (path_len < 40 || - git_oid_mkstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < GIT_SUCCESS) - memset(&p->sha1, 0x0, GIT_OID_RAWSZ); - - *pack_out = p; - return GIT_SUCCESS; -} - -static int packfile_load__cb(void *_data, char *path) +static int packfile_load__cb(void *_data, git_buf *path) { struct pack_backend *backend = (struct pack_backend *)_data; - struct pack_file *pack; + struct git_pack_file *pack; int error; - size_t i; + unsigned int i; - if (git__suffixcmp(path, ".idx") != 0) - return GIT_SUCCESS; /* not an index */ + if (git__suffixcmp(path->ptr, ".idx") != 0) + return 0; /* not an index */ for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p = git_vector_get(&backend->packs, i); - if (memcmp(p->pack_name, path, strlen(path) - STRLEN(".idx")) == 0) - return GIT_SUCCESS; + struct git_pack_file *p = git_vector_get(&backend->packs, i); + if (memcmp(p->pack_name, git_buf_cstr(path), git_buf_len(path) - strlen(".idx")) == 0) + return 0; } - error = packfile_check(&pack, path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load packfile"); + error = git_packfile_check(&pack, path->ptr); + if (error == GIT_ENOTFOUND) + /* ignore missing .pack file as git does */ + return 0; + else if (error < 0) + return error; - if (git_vector_insert(&backend->packs, pack) < GIT_SUCCESS) { - free(pack); - return GIT_ENOMEM; - } - - return GIT_SUCCESS; + return git_vector_insert(&backend->packs, pack); } static int packfile_refresh_all(struct pack_backend *backend) @@ -897,534 +239,104 @@ static int packfile_refresh_all(struct pack_backend *backend) struct stat st; if (backend->pack_folder == NULL) - return GIT_SUCCESS; + return 0; - if (gitfo_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "Failed to refresh packfiles. Backend not found"); + if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) + return git_odb__error_notfound("failed to refresh packfiles", NULL); if (st.st_mtime != backend->pack_folder_mtime) { - char path[GIT_PATH_MAX]; - strcpy(path, backend->pack_folder); + git_buf path = GIT_BUF_INIT; + git_buf_sets(&path, backend->pack_folder); /* reload all packs */ - error = gitfo_dirent(path, GIT_PATH_MAX, packfile_load__cb, (void *)backend); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to refresh packfiles"); + error = git_path_direach(&path, packfile_load__cb, (void *)backend); + + git_buf_free(&path); + + if (error < 0) + return error; git_vector_sort(&backend->packs); backend->pack_folder_mtime = st.st_mtime; } - return GIT_SUCCESS; + return 0; } - - - - - - - -/*********************************************************** - * - * PACKFILE ENTRY SEARCH INTERNALS - * - ***********************************************************/ - -static off_t nth_packed_object_offset(const struct pack_file *p, uint32_t n) -{ - const unsigned char *index = p->index_map.data; - index += 4 * 256; - if (p->index_version == 1) { - return ntohl(*((uint32_t *)(index + 24 * n))); - } else { - uint32_t off; - index += 8 + p->num_objects * (20 + 4); - off = ntohl(*((uint32_t *)(index + 4 * n))); - if (!(off & 0x80000000)) - return off; - index += p->num_objects * 4 + (off & 0x7fffffff) * 8; - return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | - ntohl(*((uint32_t *)(index + 4))); - } -} - -static int pack_entry_find_offset( - off_t *offset_out, - git_oid *found_oid, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len) -{ - const uint32_t *level1_ofs = p->index_map.data; - const unsigned char *index = p->index_map.data; - unsigned hi, lo, stride; - int pos, found = 0; - const unsigned char *current; - - *offset_out = 0; - - if (index == NULL) { - int error; - - if ((error = pack_index_open(p)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find offset for pack entry"); - - assert(p->index_map.data); - - index = p->index_map.data; - level1_ofs = p->index_map.data; - } - - if (p->index_version > 1) { - level1_ofs += 2; - index += 8; - } - - index += 4 * 256; - hi = ntohl(level1_ofs[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); - - if (p->index_version > 1) { - stride = 20; - } else { - stride = 24; - index += 4; - } - -#ifdef INDEX_DEBUG_LOOKUP - printf("%02x%02x%02x... lo %u hi %u nr %d\n", - short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); -#endif - - /* Use git.git lookup code */ - pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = index + pos * stride; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = - 1 - pos; - if (pos < (int)p->num_objects) { - current = index + pos * stride; - - if (!git_oid_match_raw(len, short_oid->id, current)) { - found = 1; - } - } - } - - if (found && pos + 1 < (int)p->num_objects) { - /* Check for ambiguousity */ - const unsigned char *next = current + stride; - - if (!git_oid_match_raw(len, short_oid->id, next)) { - found = 2; - } - } - - if (!found) { - return git__throw(GIT_ENOTFOUND, "Failed to find offset for pack entry. Entry not found"); - } else if (found > 1) { - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find offset for pack entry. Ambiguous sha1 prefix within pack"); - } else { - *offset_out = nth_packed_object_offset(p, pos); - git_oid_mkraw(found_oid, current); - -#ifdef INDEX_DEBUG_LOOKUP - unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; - git_oid_fmt(hex_sha1, found_oid); - hex_sha1[GIT_OID_HEXSZ] = '\0'; - printf("found lo=%d %s\n", lo, hex_sha1); -#endif - return GIT_SUCCESS; - } -} - -static int pack_entry_find1( - struct pack_entry *e, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len) -{ - off_t offset; - git_oid found_oid; - int error; - - assert(p); - - if (len == GIT_OID_HEXSZ && p->num_bad_objects) { - unsigned i; - for (i = 0; i < p->num_bad_objects; i++) - if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0) - return git__throw(GIT_ERROR, "Failed to find pack entry. Bad object found"); - } - - error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry. Couldn't find offset"); - - /* we found a unique entry in the index; - * make sure the packfile backing the index - * still exists on disk */ - if (p->pack_fd == -1 && packfile_open(p) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to find pack entry. Packfile doesn't exist on disk"); - - e->offset = offset; - e->p = p; - - git_oid_cpy(&e->sha1, &found_oid); - return GIT_SUCCESS; -} - -static int pack_entry_find(struct pack_entry *e, struct pack_backend *backend, const git_oid *oid) +static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) { int error; - size_t i; + unsigned int i; - if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry"); + if ((error = packfile_refresh_all(backend)) < 0) + return error; if (backend->last_found && - pack_entry_find1(e, backend->last_found, oid, GIT_OID_HEXSZ) == GIT_SUCCESS) - return GIT_SUCCESS; + git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) + return 0; for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p; + struct git_pack_file *p; p = git_vector_get(&backend->packs, i); if (p == backend->last_found) continue; - if (pack_entry_find1(e, p, oid, GIT_OID_HEXSZ) == GIT_SUCCESS) { + if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { backend->last_found = p; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_ENOTFOUND, "Failed to find pack entry"); + return git_odb__error_notfound("failed to find pack entry", oid); } static int pack_entry_find_prefix( - struct pack_entry *e, + struct git_pack_entry *e, struct pack_backend *backend, const git_oid *short_oid, unsigned int len) { int error; - size_t i; + unsigned int i; unsigned found = 0; - if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry"); + if ((error = packfile_refresh_all(backend)) < 0) + return error; if (backend->last_found) { - error = pack_entry_find1(e, backend->last_found, short_oid, len); - if (error == GIT_EAMBIGUOUSOIDPREFIX) { - return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else if (error == GIT_SUCCESS) { + error = git_pack_entry_find(e, backend->last_found, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) found = 1; - } } for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p; + struct git_pack_file *p; p = git_vector_get(&backend->packs, i); if (p == backend->last_found) continue; - error = pack_entry_find1(e, p, short_oid, len); - if (error == GIT_EAMBIGUOUSOIDPREFIX) { - return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else if (error == GIT_SUCCESS) { - found++; - if (found > 1) + error = git_pack_entry_find(e, p, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (++found > 1) break; backend->last_found = p; } } - if (!found) { - return git__rethrow(GIT_ENOTFOUND, "Failed to find pack entry"); - } else if (found > 1) { - return git__rethrow(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else { - return GIT_SUCCESS; - } - -} - - - - - - - - - - - - -/*********************************************************** - * - * PACKFILE ENTRY UNPACK INTERNALS - * - ***********************************************************/ - -static unsigned long packfile_unpack_header1( - size_t *sizep, - git_otype *type, - const unsigned char *buf, - unsigned long len) -{ - unsigned shift; - unsigned long size, c; - unsigned long used = 0; - - c = buf[used++]; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used || bitsizeof(long) <= shift) - return 0; - - c = buf[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - *sizep = (size_t)size; - return used; -} - -static int packfile_unpack_header( - size_t *size_p, - git_otype *type_p, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos) -{ - unsigned char *base; - unsigned int left; - unsigned long used; - - /* pack_window_open() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash - * size that is assured.) With our object header encoding - * the maximum deflated object size is 2^137, which is just - * insane, so we know won't exceed what we have been given. - */ - base = pack_window_open(backend, p, w_curs, *curpos, &left); - if (base == NULL) - return GIT_ENOMEM; - - used = packfile_unpack_header1(size_p, type_p, base, left); - - if (used == 0) - return git__throw(GIT_EOBJCORRUPTED, "Header length is zero"); - - *curpos += used; - return GIT_SUCCESS; -} - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t size, - git_otype type) -{ - int st; - z_stream stream; - unsigned char *buffer, *in; - - buffer = git__malloc(size); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = buffer; - stream.avail_out = size + 1; - - st = inflateInit(&stream); - if (st != Z_OK) { - free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); - } - - do { - in = pack_window_open(backend, p, w_curs, curpos, &stream.avail_in); - stream.next_in = in; - st = inflate(&stream, Z_FINISH); - - if (!stream.avail_out) - break; /* the payload is larger than it should be */ - - curpos += stream.next_in - in; - } while (st == Z_OK || st == Z_BUF_ERROR); - - inflateEnd(&stream); - - if ((st != Z_STREAM_END) || stream.total_out != size) { - free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); - } - - obj->type = type; - obj->len = size; - obj->data = buffer; - return GIT_SUCCESS; -} - -static off_t get_delta_base( - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos, - git_otype type, - off_t delta_obj_offset) -{ - unsigned char *base_info = pack_window_open(backend, p, w_curs, *curpos, NULL); - off_t base_offset; - git_oid unused; - - /* pack_window_open() assured us we have [base_info, base_info + 20) - * as a range that we can look at without walking off the - * end of the mapped window. Its actually the hash size - * that is assured. An OFS_DELTA longer than the hash size - * is stupid, as then a REF_DELTA would be smaller to store. - */ - if (type == GIT_OBJ_OFS_DELTA) { - unsigned used = 0; - unsigned char c = base_info[used++]; - base_offset = c & 127; - while (c & 128) { - base_offset += 1; - if (!base_offset || MSB(base_offset, 7)) - return 0; /* overflow */ - c = base_info[used++]; - base_offset = (base_offset << 7) + (c & 127); - } - base_offset = delta_obj_offset - base_offset; - if (base_offset <= 0 || base_offset >= delta_obj_offset) - return 0; /* out of bound */ - *curpos += used; - } else if (type == GIT_OBJ_REF_DELTA) { - /* The base entry _must_ be in the same pack */ - if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < GIT_SUCCESS) - return git__throw(GIT_EPACKCORRUPTED, "Base entry delta is not in the same pack"); - *curpos += 20; - } else + 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; - - return base_offset; } -static int packfile_unpack_delta( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t delta_size, - git_otype delta_type, - off_t obj_offset) -{ - off_t base_offset; - git_rawobj base, delta; - int error; - - base_offset = get_delta_base(backend, p, w_curs, &curpos, delta_type, obj_offset); - if (base_offset == 0) - return git__throw(GIT_EOBJCORRUPTED, "Delta offset is zero"); - - pack_window_close(w_curs); - error = packfile_unpack(&base, backend, p, base_offset); - - /* TODO: git.git tries to load the base from other packfiles - * or loose objects */ - if (error < GIT_SUCCESS) - return git__rethrow(error, "Corrupted delta"); - - error = packfile_unpack_compressed(&delta, backend, p, w_curs, curpos, delta_size, delta_type); - if (error < GIT_SUCCESS) { - free(base.data); - return git__rethrow(error, "Corrupted delta"); - } - - obj->type = base.type; - error = git__delta_apply(obj, - base.data, base.len, - delta.data, delta.len); - - free(base.data); - free(delta.data); - - /* TODO: we might want to cache this shit. eventually */ - //add_delta_base_cache(p, base_offset, base, base_size, *type); - return error; /* error set by git__delta_apply */ -} - -static int packfile_unpack( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - off_t obj_offset) -{ - struct pack_window *w_curs = NULL; - off_t curpos = obj_offset; - int error; - - size_t size = 0; - git_otype type; - - /* - * TODO: optionally check the CRC on the packfile - */ - - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJ_BAD; - - error = packfile_unpack_header(&size, &type, backend, p, &w_curs, &curpos); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to unpack packfile"); - - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - error = packfile_unpack_delta( - obj, backend, p, &w_curs, curpos, - size, type, obj_offset); - break; - - case GIT_OBJ_COMMIT: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_TAG: - error = packfile_unpack_compressed( - obj, backend, p, &w_curs, curpos, - size, type); - break; - - default: - error = GIT_EOBJCORRUPTED; - break; - } - - pack_window_close(&w_curs); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to unpack packfile"); -} - - - - /*********************************************************** * @@ -1448,26 +360,24 @@ int pack_backend__read_header(git_rawobj *obj, git_odb_backend *backend, const g } */ -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(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - struct pack_entry e; + struct git_pack_entry e; git_rawobj raw; int error; - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - - if ((error = packfile_unpack(&raw, (struct pack_backend *)backend, e.p, e.offset)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || + (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) + return error; *buffer_p = raw.data; *len_p = raw.len; *type_p = raw.type; - return GIT_SUCCESS; + return 0; } -int pack_backend__read_prefix( +static int pack_backend__read_prefix( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -1476,87 +386,77 @@ int pack_backend__read_prefix( const git_oid *short_oid, unsigned int len) { + int error = 0; + if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read pack backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + error = git_odb__error_ambiguous("prefix length too short"); - if (len >= GIT_OID_HEXSZ) { + else if (len >= GIT_OID_HEXSZ) { /* We can fall back to regular read method */ - int error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) + error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) git_oid_cpy(out_oid, short_oid); - - return error; } else { - struct pack_entry e; + struct git_pack_entry e; git_rawobj raw; - int error; - if ((error = pack_entry_find_prefix(&e, (struct pack_backend *)backend, short_oid, len)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - - if ((error = packfile_unpack(&raw, (struct pack_backend *)backend, e.p, e.offset)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - git_oid_cpy(out_oid, &e.sha1); + if ((error = pack_entry_find_prefix( + &e, (struct pack_backend *)backend, short_oid, len)) == 0 && + (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + git_oid_cpy(out_oid, &e.sha1); + } } - return GIT_SUCCESS; + return error; } -int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) +static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) { - struct pack_entry e; - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == GIT_SUCCESS; + struct git_pack_entry e; + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } -void pack_backend__free(git_odb_backend *_backend) +static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; - size_t i; + unsigned int i; assert(_backend); backend = (struct pack_backend *)_backend; for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p = git_vector_get(&backend->packs, i); - packfile_free(backend, p); + struct git_pack_file *p = git_vector_get(&backend->packs, i); + packfile_free(p); } git_vector_free(&backend->packs); - free(backend->pack_folder); - free(backend); + git__free(backend->pack_folder); + git__free(backend); } int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) { - struct pack_backend *backend; - char path[GIT_PATH_MAX]; + struct pack_backend *backend = NULL; + git_buf path = GIT_BUF_INIT; backend = git__calloc(1, sizeof(struct pack_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); - if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < GIT_SUCCESS) { - free(backend); - return GIT_ENOMEM; + if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < 0 || + git_buf_joinpath(&path, objects_dir, "pack") < 0) + { + git__free(backend); + return -1; } - backend->window_size = DEFAULT_WINDOW_SIZE; - backend->mapped_limit = DEFAULT_MAPPED_LIMIT; - - git__joinpath(path, objects_dir, "pack"); - if (gitfo_isdir(path) == GIT_SUCCESS) { - backend->pack_folder = git__strdup(path); + if (git_path_isdir(git_buf_cstr(&path)) == true) { + backend->pack_folder = git_buf_detach(&path); backend->pack_folder_mtime = 0; - - if (backend->pack_folder == NULL) { - free(backend); - return GIT_ENOMEM; - } } backend->parent.read = &pack_backend__read; @@ -1566,5 +466,8 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) backend->parent.free = &pack_backend__free; *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; + + git_buf_free(&path); + + return 0; } diff --git a/src/oid.c b/src/oid.c index 8dc6903cd..87756010b 100644 --- a/src/oid.c +++ b/src/oid.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -29,37 +11,52 @@ #include #include -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 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ -}; static char to_hex[] = "0123456789abcdef"; -int git_oid_mkstr(git_oid *out, const char *str) +static int oid_error_invalid(const char *msg) +{ + giterr_set(GITERR_INVALID, "Unable to parse OID - %s", msg); + return -1; +} + +int git_oid_fromstrn(git_oid *out, const char *str, size_t length) { size_t p; - for (p = 0; p < sizeof(out->id); p++, str += 2) { - int v = (from_hex[(unsigned char)str[0]] << 4) - | from_hex[(unsigned char)str[1]]; + int v; + + if (length < 4) + return oid_error_invalid("input too short"); + + if (length > GIT_OID_HEXSZ) + length = GIT_OID_HEXSZ; + + for (p = 0; p < length - 1; p += 2) { + v = (git__fromhex(str[p + 0]) << 4) + | git__fromhex(str[p + 1]); + if (v < 0) - return git__throw(GIT_ENOTOID, "Failed to generate sha1. Given string is not a valid sha1 hash"); - out->id[p] = (unsigned char)v; + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] = (unsigned char)v; } - return GIT_SUCCESS; + + if (length % 2) { + v = (git__fromhex(str[p + 0]) << 4); + if (v < 0) + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] = (unsigned char)v; + p += 2; + } + + memset(out->id + p / 2, 0, (GIT_OID_HEXSZ - p) / 2); + + return 0; +} + +int git_oid_fromstr(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); } GIT_INLINE(char) *fmt_one(char *str, unsigned int val) @@ -97,14 +94,14 @@ char *git_oid_allocfmt(const git_oid *oid) return str; } -char *git_oid_to_string(char *out, size_t n, const git_oid *oid) +char *git_oid_tostr(char *out, size_t n, const git_oid *oid) { char str[GIT_OID_HEXSZ]; if (!out || n == 0 || !oid) return ""; - n--; /* allow room for terminating NUL */ + n--; /* allow room for terminating NUL */ if (n > 0) { git_oid_fmt(str, oid); @@ -118,8 +115,9 @@ char *git_oid_to_string(char *out, size_t n, const git_oid *oid) return out; } -int git__parse_oid(git_oid *oid, const char **buffer_out, - const char *buffer_end, const char *header) +int git_oid__parse( + git_oid *oid, const char **buffer_out, + const char *buffer_end, const char *header) { const size_t sha_len = GIT_OID_HEXSZ; const size_t header_len = strlen(header); @@ -127,37 +125,33 @@ int git__parse_oid(git_oid *oid, const char **buffer_out, const char *buffer = *buffer_out; if (buffer + (header_len + sha_len + 1) > buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer too small"); + return -1; if (memcmp(buffer, header, header_len) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer and header do not match"); + return -1; if (buffer[header_len + sha_len] != '\n') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer not terminated correctly"); + return -1; - if (git_oid_mkstr(oid, buffer + header_len) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Failed to generate sha1"); + if (git_oid_fromstr(oid, buffer + header_len) < 0) + return -1; *buffer_out = buffer + (header_len + sha_len + 1); - return GIT_SUCCESS; + return 0; } -int git__write_oid(git_odb_stream *stream, const char *header, const git_oid *oid) +void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid) { - char hex_oid[42]; + char hex_oid[GIT_OID_HEXSZ]; - git_oid_fmt(hex_oid + 1, oid); - - hex_oid[0] = ' '; - hex_oid[41] = '\n'; - - stream->write(stream, header, strlen(header)); - stream->write(stream, hex_oid, 42); - return GIT_SUCCESS; + git_oid_fmt(hex_oid, oid); + git_buf_puts(buf, header); + git_buf_put(buf, hex_oid, GIT_OID_HEXSZ); + git_buf_putc(buf, '\n'); } -void git_oid_mkraw(git_oid *out, const unsigned char *raw) +void git_oid_fromraw(git_oid *out, const unsigned char *raw) { memcpy(out->id, raw, sizeof(out->id)); } @@ -172,30 +166,44 @@ int git_oid_cmp(const git_oid *a, const git_oid *b) return memcmp(a->id, b->id, sizeof(a->id)); } - -int git_oid_match_raw(unsigned int len, const unsigned char *a, const unsigned char *b) +int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len) { - do { - if (*a != *b) - return 1; - a++; - b++; - len -= 2; - } while (len > 1); - if (len) - if ((*a ^ *b) & 0xf0) - return 1; - return 0; + const unsigned char *a = oid_a->id; + const unsigned char *b = oid_b->id; + + do { + if (*a != *b) + return 1; + a++; + b++; + len -= 2; + } while (len > 1); + + if (len) + if ((*a ^ *b) & 0xf0) + return 1; + + return 0; } -int git_oid_match_hex(unsigned int len, const unsigned char *a, const unsigned char *b) +int git_oid_streq(const git_oid *a, const char *str) { - return memcmp(a, b, len); + git_oid id; + + if (git_oid_fromstr(&id, str) < 0) + return -1; + + return git_oid_cmp(a, &id) == 0 ? 0 : -1; } -int gid_oid_match(unsigned int len, git_oid *a, git_oid *b) +int git_oid_iszero(const git_oid *oid_a) { - return git_oid_match_raw(len, a->id, b->id); + const unsigned char *a = oid_a->id; + unsigned int i; + for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) + if (*a != 0) + return 0; + return 1; } typedef short node_index; @@ -213,16 +221,15 @@ struct git_oid_shorten { static int resize_trie(git_oid_shorten *self, size_t new_size) { - self->nodes = realloc(self->nodes, new_size * sizeof(trie_node)); - if (self->nodes == NULL) - return GIT_ENOMEM; + self->nodes = git__realloc(self->nodes, new_size * sizeof(trie_node)); + GITERR_CHECK_ALLOC(self->nodes); if (new_size > self->size) { memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); } self->size = new_size; - return GIT_SUCCESS; + return 0; } static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) @@ -231,7 +238,7 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co node_index idx_leaf; if (os->node_count >= os->size) { - if (resize_trie(os, os->size * 2) < GIT_SUCCESS) + if (resize_trie(os, os->size * 2) < 0) return NULL; } @@ -253,27 +260,27 @@ git_oid_shorten *git_oid_shorten_new(size_t min_length) { git_oid_shorten *os; - os = git__malloc(sizeof(git_oid_shorten)); + assert((size_t)((int)min_length) == min_length); + + os = git__calloc(1, sizeof(git_oid_shorten)); if (os == NULL) return NULL; - memset(os, 0x0, sizeof(git_oid_shorten)); - - if (resize_trie(os, 16) < GIT_SUCCESS) { - free(os); + if (resize_trie(os, 16) < 0) { + git__free(os); return NULL; } os->node_count = 1; - os->min_length = min_length; + os->min_length = (int)min_length; return os; } void git_oid_shorten_free(git_oid_shorten *os) { - free(os->nodes); - free(os); + git__free(os->nodes); + git__free(os); } @@ -293,7 +300,7 @@ void git_oid_shorten_free(git_oid_shorten *os) * * - Each normal node points to 16 children (one for each possible * character in the oid). This is *not* stored in an array of - * pointers, because in a 64-bit arch this would be sucking + * pointers, because in a 64-bit arch this would be sucking * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking * insane. What we do is store Node Indexes, and use these indexes * to look up each node in the om->index array. These indexes are @@ -323,21 +330,27 @@ void git_oid_shorten_free(git_oid_shorten *os) */ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) { - int i, is_leaf; + int i; + bool is_leaf; node_index idx; if (os->full) - return GIT_ENOMEM; + return -1; + + if (text_oid == NULL) + return os->min_length; idx = 0; - is_leaf = 0; + is_leaf = false; for (i = 0; i < GIT_OID_HEXSZ; ++i) { - int c = from_hex[(int)text_oid[i]]; + int c = git__fromhex(text_oid[i]); trie_node *node; - if (c == -1) - return git__throw(GIT_ENOTOID, "Failed to shorten OID. Invalid hex value"); + if (c == -1) { + giterr_set(GITERR_INVALID, "Unable to shorten OID - invalid hex value"); + return -1; + } node = &os->nodes[idx]; @@ -347,23 +360,22 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) tail = node->tail; node->tail = NULL; - node = push_leaf(os, idx, from_hex[(int)tail[0]], &tail[1]); - if (node == NULL) - return GIT_ENOMEM; + node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); + GITERR_CHECK_ALLOC(node); } if (node->children[c] == 0) { if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) - return GIT_ENOMEM; + return -1; break; } idx = node->children[c]; - is_leaf = 0; + is_leaf = false; if (idx < 0) { node->children[c] = idx = -idx; - is_leaf = 1; + is_leaf = true; } } diff --git a/src/oid.h b/src/oid.h deleted file mode 100644 index 1cd2450d6..000000000 --- a/src/oid.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef INCLUDE_oid_h__ -#define INCLUDE_oid_h__ - -/** - * Compare the first ('len'*4) bits of two raw formatted oids. - * This can be useful for internal use. - * Return 0 if they match. - */ -int git_oid_match_raw(unsigned int len, const unsigned char *a, const unsigned char *b); - -/** - * Compare the first 'len' characters of two hex formatted oids. - * Return 0 if they match. - */ -int git_oid_match_hex(unsigned int len, const unsigned char *a, const unsigned char *b); - -#endif diff --git a/src/oidmap.h b/src/oidmap.h new file mode 100644 index 000000000..858268c92 --- /dev/null +++ b/src/oidmap.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_oidmap_h__ +#define INCLUDE_oidmap_h__ + +#include "common.h" +#include "git2/oid.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(oid, const git_oid *, void *); +typedef khash_t(oid) git_oidmap; + +GIT_INLINE(khint_t) hash_git_oid(const git_oid *oid) +{ + int i; + khint_t h = 0; + for (i = 0; i < 20; ++i) + h = (h << 5) - h + oid->id[i]; + return h; +} + +GIT_INLINE(int) hash_git_oid_equal(const git_oid *a, const git_oid *b) +{ + return (memcmp(a->id, b->id, sizeof(a->id)) == 0); +} + +#define GIT__USE_OIDMAP \ + __KHASH_IMPL(oid, static inline, const git_oid *, void *, 1, hash_git_oid, hash_git_oid_equal) + +#define git_oidmap_alloc() kh_init(oid) +#define git_oidmap_free(h) kh_destroy(oid,h), h = NULL + +#endif diff --git a/src/pack.c b/src/pack.c new file mode 100644 index 000000000..0db1069de --- /dev/null +++ b/src/pack.c @@ -0,0 +1,816 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "odb.h" +#include "pack.h" +#include "delta-apply.h" +#include "sha1_lookup.h" +#include "mwindow.h" +#include "fileops.h" + +#include "git2/oid.h" +#include + +static int packfile_open(struct git_pack_file *p); +static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); +int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t size, + git_otype type); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid + * is ambiguous within the pack. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_offset( + git_off_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len); + +static int packfile_error(const char *message) +{ + giterr_set(GITERR_ODB, "Invalid pack file - %s", message); + return -1; +} + +/*********************************************************** + * + * PACK INDEX METHODS + * + ***********************************************************/ + +static void pack_index_free(struct git_pack_file *p) +{ + if (p->index_map.data) { + git_futils_mmap_free(&p->index_map); + p->index_map.data = NULL; + } +} + +static int pack_index_check(const char *path, struct git_pack_file *p) +{ + struct git_pack_idx_header *hdr; + uint32_t version, nr, i, *index; + void *idx_map; + size_t idx_size; + struct stat st; + int error; + /* TODO: properly open the file without access time using O_NOATIME */ + git_file fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0 || + !S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) + { + p_close(fd); + giterr_set(GITERR_OS, "Failed to check pack index."); + return -1; + } + + error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); + + p_close(fd); + + if (error < 0) + return error; + + hdr = idx_map = p->index_map.data; + + if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { + version = ntohl(hdr->idx_version); + + if (version < 2 || version > 2) { + git_futils_mmap_free(&p->index_map); + return packfile_error("unsupported index version"); + } + + } else + version = 1; + + nr = 0; + index = idx_map; + + if (version > 1) + index += 2; /* skip index header */ + + for (i = 0; i < 256; i++) { + uint32_t n = ntohl(index[i]); + if (n < nr) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is non-monotonic"); + } + nr = n; + } + + if (version == 1) { + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is corrupted"); + } + } else if (version == 2) { + /* + * Minimum size: + * - 8 bytes of header + * - 256 index entries 4 bytes each + * - 20-byte sha1 entry * nr + * - 4-byte crc entry * nr + * - 4-byte offset entry * nr + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + * And after the 4-byte offset table might be a + * variable sized table containing 8-byte entries + * for offsets larger than 2^31. + */ + unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; + unsigned long max_size = min_size; + + if (nr) + max_size += (nr - 1)*8; + + if (idx_size < min_size || idx_size > max_size) { + git_futils_mmap_free(&p->index_map); + return packfile_error("wrong index size"); + } + } + + p->index_version = version; + p->num_objects = nr; + return 0; +} + +static int pack_index_open(struct git_pack_file *p) +{ + char *idx_name; + int error; + size_t name_len, offset; + + if (p->index_map.data) + return 0; + + idx_name = git__strdup(p->pack_name); + GITERR_CHECK_ALLOC(idx_name); + + name_len = strlen(idx_name); + offset = name_len - strlen(".pack"); + assert(offset < name_len); /* make sure no underflow */ + + strncpy(idx_name + offset, ".idx", name_len - offset); + + error = pack_index_check(idx_name, p); + git__free(idx_name); + + return error; +} + +static unsigned char *pack_window_open( + struct git_pack_file *p, + git_mwindow **w_cursor, + git_off_t offset, + unsigned int *left) +{ + if (p->mwf.fd == -1 && packfile_open(p) < 0) + return NULL; + + /* Since packfiles end in a hash of their content and it's + * pointless to ask for an offset into the middle of that + * hash, and the pack_window_contains function above wouldn't match + * don't allow an offset too close to the end of the file. + */ + if (offset > (p->mwf.size - 20)) + return NULL; + + return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); + } + +static int packfile_unpack_header1( + unsigned long *usedp, + size_t *sizep, + git_otype *type, + const unsigned char *buf, + unsigned long len) +{ + unsigned shift; + unsigned long size, c; + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + return GIT_EBUFS; + + if (bitsizeof(long) <= shift) { + *usedp = 0; + return -1; + } + + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + *sizep = (size_t)size; + *usedp = used; + return 0; +} + +int git_packfile_unpack_header( + size_t *size_p, + git_otype *type_p, + git_mwindow_file *mwf, + git_mwindow **w_curs, + git_off_t *curpos) +{ + unsigned char *base; + unsigned int left; + unsigned long used; + int ret; + + /* pack_window_open() assures us we have [base, base + 20) available + * as a range that we can look at at. (Its actually the hash + * size that is assured.) With our object header encoding + * the maximum deflated object size is 2^137, which is just + * insane, so we know won't exceed what we have been given. + */ +// base = pack_window_open(p, w_curs, *curpos, &left); + base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left); + if (base == NULL) + return GIT_EBUFS; + + ret = packfile_unpack_header1(&used, size_p, type_p, base, left); + git_mwindow_close(w_curs); + if (ret == GIT_EBUFS) + return ret; + else if (ret < 0) + return packfile_error("header length is zero"); + + *curpos += used; + return 0; +} + +static int packfile_unpack_delta( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t delta_size, + git_otype delta_type, + git_off_t obj_offset) +{ + git_off_t base_offset; + git_rawobj base, delta; + int error; + + base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); + git_mwindow_close(w_curs); + if (base_offset == 0) + return packfile_error("delta offset is zero"); + if (base_offset < 0) /* must actually be an error code */ + return (int)base_offset; + + error = git_packfile_unpack(&base, p, &base_offset); + + /* + * TODO: git.git tries to load the base from other packfiles + * or loose objects. + * + * We'll need to do this in order to support thin packs. + */ + if (error < 0) + return error; + + error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); + git_mwindow_close(w_curs); + if (error < 0) { + git__free(base.data); + return error; + } + + obj->type = base.type; + error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + + git__free(base.data); + git__free(delta.data); + + /* TODO: we might want to cache this shit. eventually */ + //add_delta_base_cache(p, base_offset, base, base_size, *type); + + return error; /* error set by git__delta_apply */ +} + +int git_packfile_unpack( + git_rawobj *obj, + struct git_pack_file *p, + git_off_t *obj_offset) +{ + git_mwindow *w_curs = NULL; + git_off_t curpos = *obj_offset; + int error; + + size_t size = 0; + git_otype type; + + /* + * TODO: optionally check the CRC on the packfile + */ + + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJ_BAD; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + + switch (type) { + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + error = packfile_unpack_delta( + obj, p, &w_curs, &curpos, + size, type, *obj_offset); + break; + + case GIT_OBJ_COMMIT: + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + case GIT_OBJ_TAG: + error = packfile_unpack_compressed( + obj, p, &w_curs, &curpos, + size, type); + break; + + default: + error = packfile_error("invalid packfile type in header");; + break; + } + + *obj_offset = curpos; + return error; +} + +static void *use_git_alloc(void *opaq, unsigned int count, unsigned int size) +{ + GIT_UNUSED(opaq); + return git__calloc(count, size); +} + +static void use_git_free(void *opaq, void *ptr) +{ + GIT_UNUSED(opaq); + git__free(ptr); +} + +int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t size, + git_otype type) +{ + int st; + z_stream stream; + unsigned char *buffer, *in; + + buffer = git__calloc(1, size + 1); + GITERR_CHECK_ALLOC(buffer); + + memset(&stream, 0, sizeof(stream)); + stream.next_out = buffer; + stream.avail_out = (uInt)size + 1; + stream.zalloc = use_git_alloc; + stream.zfree = use_git_free; + + st = inflateInit(&stream); + if (st != Z_OK) { + git__free(buffer); + giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + + return -1; + } + + do { + in = pack_window_open(p, w_curs, *curpos, &stream.avail_in); + stream.next_in = in; + st = inflate(&stream, Z_FINISH); + git_mwindow_close(w_curs); + + if (!stream.avail_out) + break; /* the payload is larger than it should be */ + + if (st == Z_BUF_ERROR && in == NULL) { + inflateEnd(&stream); + git__free(buffer); + return GIT_EBUFS; + } + + *curpos += stream.next_in - in; + } while (st == Z_OK || st == Z_BUF_ERROR); + + inflateEnd(&stream); + + if ((st != Z_STREAM_END) || stream.total_out != size) { + git__free(buffer); + giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + return -1; + } + + obj->type = type; + obj->len = size; + obj->data = buffer; + return 0; +} + +/* + * curpos is where the data starts, delta_obj_offset is the where the + * header starts + */ +git_off_t get_delta_base( + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + git_otype type, + git_off_t delta_obj_offset) +{ + unsigned int left = 0; + unsigned char *base_info; + git_off_t base_offset; + git_oid unused; + + base_info = pack_window_open(p, w_curs, *curpos, &left); + /* Assumption: the only reason this would fail is because the file is too small */ + if (base_info == NULL) + return GIT_EBUFS; + /* pack_window_open() assured us we have [base_info, base_info + 20) + * as a range that we can look at without walking off the + * end of the mapped window. Its actually the hash size + * that is assured. An OFS_DELTA longer than the hash size + * is stupid, as then a REF_DELTA would be smaller to store. + */ + if (type == GIT_OBJ_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + base_offset = c & 127; + while (c & 128) { + if (left <= used) + return GIT_EBUFS; + base_offset += 1; + if (!base_offset || MSB(base_offset, 7)) + return 0; /* overflow */ + c = base_info[used++]; + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = delta_obj_offset - base_offset; + if (base_offset <= 0 || base_offset >= delta_obj_offset) + return 0; /* out of bound */ + *curpos += used; + } else if (type == GIT_OBJ_REF_DELTA) { + /* If we have the cooperative cache, search in it first */ + if (p->has_cache) { + int pos; + struct git_pack_entry key; + + git_oid_fromraw(&key.sha1, base_info); + pos = git_vector_bsearch(&p->cache, &key); + if (pos >= 0) { + *curpos += 20; + return ((struct git_pack_entry *)git_vector_get(&p->cache, pos))->offset; + } + } + /* The base entry _must_ be in the same pack */ + if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0) + return packfile_error("base entry delta is not in the same pack"); + *curpos += 20; + } else + return 0; + + return base_offset; +} + +/*********************************************************** + * + * PACKFILE METHODS + * + ***********************************************************/ + +static struct git_pack_file *packfile_alloc(size_t extra) +{ + struct git_pack_file *p = git__calloc(1, sizeof(*p) + extra); + if (p != NULL) + p->mwf.fd = -1; + return p; +} + + +void packfile_free(struct git_pack_file *p) +{ + assert(p); + + /* clear_delta_base_cache(); */ + git_mwindow_free_all(&p->mwf); + + if (p->mwf.fd != -1) + p_close(p->mwf.fd); + + pack_index_free(p); + + git__free(p->bad_object_sha1); + git__free(p); +} + +static int packfile_open(struct git_pack_file *p) +{ + struct stat st; + struct git_pack_header hdr; + git_oid sha1; + unsigned char *idx_sha1; + + assert(p->index_map.data); + + if (!p->index_map.data && pack_index_open(p) < 0) + return git_odb__error_notfound("failed to open packfile", NULL); + + /* TODO: open with noatime */ + p->mwf.fd = git_futils_open_ro(p->pack_name); + if (p->mwf.fd < 0) + return p->mwf.fd; + + if (p_fstat(p->mwf.fd, &st) < 0 || + git_mwindow_file_register(&p->mwf) < 0) + goto cleanup; + + /* If we created the struct before we had the pack we lack size. */ + if (!p->mwf.size) { + if (!S_ISREG(st.st_mode)) + goto cleanup; + p->mwf.size = (git_off_t)st.st_size; + } else if (p->mwf.size != st.st_size) + goto cleanup; + +#if 0 + /* We leave these file descriptors open with sliding mmap; + * there is no point keeping them open across exec(), though. + */ + fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); + if (fd_flag < 0) + goto cleanup; + + fd_flag |= FD_CLOEXEC; + if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) + goto cleanup; +#endif + + /* Verify we recognize this pack file format. */ + if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || + hdr.hdr_signature != htonl(PACK_SIGNATURE) || + !pack_version_ok(hdr.hdr_version)) + goto cleanup; + + /* Verify the pack matches its index. */ + if (p->num_objects != ntohl(hdr.hdr_entries) || + p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1 || + p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < 0) + goto cleanup; + + idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; + + if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) == 0) + return 0; + +cleanup: + giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name); + p_close(p->mwf.fd); + p->mwf.fd = -1; + return -1; +} + +int git_packfile_check(struct git_pack_file **pack_out, const char *path) +{ + struct stat st; + struct git_pack_file *p; + size_t path_len; + + *pack_out = NULL; + path_len = strlen(path); + p = packfile_alloc(path_len + 2); + GITERR_CHECK_ALLOC(p); + + /* + * Make sure a corresponding .pack file exists and that + * the index looks sane. + */ + path_len -= strlen(".idx"); + if (path_len < 1) { + git__free(p); + return git_odb__error_notfound("invalid packfile path", NULL); + } + + memcpy(p->pack_name, path, path_len); + + strcpy(p->pack_name + path_len, ".keep"); + if (git_path_exists(p->pack_name) == true) + p->pack_keep = 1; + + strcpy(p->pack_name + path_len, ".pack"); + if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { + git__free(p); + return git_odb__error_notfound("packfile not found", NULL); + } + + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p->mwf.size = st.st_size; + p->pack_local = 1; + p->mtime = (git_time_t)st.st_mtime; + + /* see if we can parse the sha1 oid in the packfile name */ + if (path_len < 40 || + git_oid_fromstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < 0) + memset(&p->sha1, 0x0, GIT_OID_RAWSZ); + + *pack_out = p; + + return 0; +} + +/*********************************************************** + * + * PACKFILE ENTRY SEARCH INTERNALS + * + ***********************************************************/ + +static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) +{ + const unsigned char *index = p->index_map.data; + index += 4 * 256; + if (p->index_version == 1) { + return ntohl(*((uint32_t *)(index + 24 * n))); + } else { + uint32_t off; + index += 8 + p->num_objects * (20 + 4); + off = ntohl(*((uint32_t *)(index + 4 * n))); + if (!(off & 0x80000000)) + return off; + index += p->num_objects * 4 + (off & 0x7fffffff) * 8; + return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | + ntohl(*((uint32_t *)(index + 4))); + } +} + +static int pack_entry_find_offset( + git_off_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len) +{ + const uint32_t *level1_ofs = p->index_map.data; + const unsigned char *index = p->index_map.data; + unsigned hi, lo, stride; + int pos, found = 0; + const unsigned char *current = 0; + + *offset_out = 0; + + if (index == NULL) { + int error; + + if ((error = pack_index_open(p)) < 0) + return error; + + assert(p->index_map.data); + + index = p->index_map.data; + level1_ofs = p->index_map.data; + } + + if (p->index_version > 1) { + level1_ofs += 2; + index += 8; + } + + index += 4 * 256; + hi = ntohl(level1_ofs[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); + + if (p->index_version > 1) { + stride = 20; + } else { + stride = 24; + index += 4; + } + +#ifdef INDEX_DEBUG_LOOKUP + printf("%02x%02x%02x... lo %u hi %u nr %d\n", + short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); +#endif + + /* Use git.git lookup code */ + pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = index + pos * stride; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = - 1 - pos; + if (pos < (int)p->num_objects) { + current = index + pos * stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { + /* Check for ambiguousity */ + const unsigned char *next = current + stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { + found = 2; + } + } + + if (!found) + return git_odb__error_notfound("failed to find offset for pack entry", short_oid); + if (found > 1) + return git_odb__error_ambiguous("found multiple offsets for pack entry"); + *offset_out = nth_packed_object_offset(p, pos); + git_oid_fromraw(found_oid, current); + +#ifdef INDEX_DEBUG_LOOKUP + { + unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; + git_oid_fmt(hex_sha1, found_oid); + hex_sha1[GIT_OID_HEXSZ] = '\0'; + printf("found lo=%d %s\n", lo, hex_sha1); + } +#endif + return 0; +} + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len) +{ + git_off_t offset; + git_oid found_oid; + int error; + + assert(p); + + if (len == GIT_OID_HEXSZ && p->num_bad_objects) { + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0) + return packfile_error("bad object found in packfile"); + } + + error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); + if (error < 0) + return error; + + /* we found a unique entry in the index; + * make sure the packfile backing the index + * still exists on disk */ + if (p->mwf.fd == -1 && (error = packfile_open(p)) < 0) + return error; + + e->offset = offset; + e->p = p; + + git_oid_cpy(&e->sha1, &found_oid); + return 0; +} diff --git a/src/pack.h b/src/pack.h new file mode 100644 index 000000000..cd7a4d2e1 --- /dev/null +++ b/src/pack.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_pack_h__ +#define INCLUDE_pack_h__ + +#include "git2/oid.h" + +#include "common.h" +#include "map.h" +#include "mwindow.h" +#include "odb.h" + +#define GIT_PACK_FILE_MODE 0444 + +#define PACK_SIGNATURE 0x5041434b /* "PACK" */ +#define PACK_VERSION 2 +#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) +struct git_pack_header { + uint32_t hdr_signature; + uint32_t hdr_version; + uint32_t hdr_entries; +}; + +/* + * The first four bytes of index formats later than version 1 should + * start with this signature, as all older git binaries would find this + * value illegal and abort reading the file. + * + * This is the case because the number of objects in a packfile + * cannot exceed 1,431,660,000 as every object would need at least + * 3 bytes of data and the overall packfile cannot exceed 4 GiB with + * version 1 of the index file due to the offsets limited to 32 bits. + * Clearly the signature exceeds this maximum. + * + * Very old git binaries will also compare the first 4 bytes to the + * next 4 bytes in the index and abort with a "non-monotonic index" + * error if the second 4 byte word is smaller than the first 4 + * byte word. This would be true in the proposed future index + * format as idx_signature would be greater than idx_version. + */ + +#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ + +struct git_pack_idx_header { + uint32_t idx_signature; + uint32_t idx_version; +}; + +struct git_pack_file { + git_mwindow_file mwf; + git_map index_map; + + uint32_t num_objects; + uint32_t num_bad_objects; + git_oid *bad_object_sha1; /* array of git_oid */ + + int index_version; + git_time_t mtime; + unsigned pack_local:1, pack_keep:1, has_cache:1; + git_oid sha1; + git_vector cache; + + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[GIT_FLEX_ARRAY]; /* more */ +}; + +struct git_pack_entry { + git_off_t offset; + git_oid sha1; + struct git_pack_file *p; +}; + +int git_packfile_unpack_header( + size_t *size_p, + git_otype *type_p, + git_mwindow_file *mwf, + git_mwindow **w_curs, + git_off_t *curpos); + +int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset); +int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t size, + git_otype type); + +git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, + git_off_t *curpos, git_otype type, + git_off_t delta_obj_offset); + +void packfile_free(struct git_pack_file *p); +int git_packfile_check(struct git_pack_file **pack_out, const char *path); +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len); + +#endif diff --git a/src/path.c b/src/path.c new file mode 100644 index 000000000..84edf6d89 --- /dev/null +++ b/src/path.c @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "path.h" +#include "posix.h" +#ifdef GIT_WIN32 +#include "win32/dir.h" +#include "win32/posix.h" +#else +#include +#endif +#include +#include +#include + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_path_basename_r(git_buf *buffer, const char *path) +{ + const char *endp, *startp; + int len, result; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); + +Exit: + result = len; + + if (buffer != NULL && git_buf_set(buffer, startp, len) < 0) + return -1; + + return result; +} + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_path_dirname_r(git_buf *buffer, const char *path) +{ + const char *endp; + int result, len; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); + +#ifdef GIT_WIN32 + /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return + 'C:/' here */ + + if (len == 2 && isalpha(path[0]) && path[1] == ':') { + len = 3; + goto Exit; + } +#endif + +Exit: + result = len; + + if (buffer != NULL && git_buf_set(buffer, path, len) < 0) + return -1; + + return result; +} + + +char *git_path_dirname(const char *path) +{ + git_buf buf = GIT_BUF_INIT; + char *dirname; + + git_path_dirname_r(&buf, path); + dirname = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ + + return dirname; +} + +char *git_path_basename(const char *path) +{ + git_buf buf = GIT_BUF_INIT; + char *basename; + + git_path_basename_r(&buf, path); + basename = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ + + return basename; +} + + +const char *git_path_topdir(const char *path) +{ + size_t len; + ssize_t i; + + assert(path); + len = strlen(path); + + if (!len || path[len - 1] != '/') + return NULL; + + for (i = (ssize_t)len - 2; i >= 0; --i) + if (path[i] == '/') + break; + + return &path[i + 1]; +} + +int git_path_root(const char *path) +{ + int offset = 0; + +#ifdef GIT_WIN32 + /* Does the root of the path look like a windows drive ? */ + if (isalpha(path[0]) && (path[1] == ':')) + offset += 2; + + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/') || + (path[0] == '\\' && path[1] == '\\')) + { + offset += 2; + + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } +#endif + + if (path[offset] == '/' || path[offset] == '\\') + return offset; + + return -1; /* Not a real error - signals that path is not rooted */ +} + +int git_path_prettify(git_buf *path_out, const char *path, const char *base) +{ + char buf[GIT_PATH_MAX]; + + assert(path && path_out); + + /* construct path if needed */ + if (base != NULL && git_path_root(path) < 0) { + if (git_buf_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; + } + + if (p_realpath(path, buf) == NULL) { + /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); + + git_buf_clear(path_out); + + return error; + } + + return git_buf_sets(path_out, buf); +} + +int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base) +{ + int error = git_path_prettify(path_out, path, base); + return (error < 0) ? error : git_path_to_dir(path_out); +} + +int git_path_to_dir(git_buf *path) +{ + if (path->asize > 0 && + git_buf_len(path) > 0 && + path->ptr[git_buf_len(path) - 1] != '/') + git_buf_putc(path, '/'); + + return git_buf_oom(path) ? -1 : 0; +} + +void git_path_string_to_dir(char* path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_buf *decoded_out, const char *input) +{ + int len, hi, lo, i; + assert(decoded_out && input); + + len = (int)strlen(input); + git_buf_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_buf_putc(decoded_out, c) < 0) + return -1; + } + + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; +} + +int git_path_fromurl(git_buf *local_path_out, const char *file_url) +{ + int offset = 0, len; + + assert(local_path_out && file_url); + + if (git__prefixcmp(file_url, "file://") != 0) + return error_invalid_local_file_uri(file_url); + + offset += 7; + len = (int)strlen(file_url); + + if (offset < len && file_url[offset] == '/') + offset++; + else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0) + offset += 10; + else + return error_invalid_local_file_uri(file_url); + + if (offset >= len || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef _MSC_VER + offset--; /* A *nix absolute path starts with a forward slash */ +#endif + + git_buf_clear(local_path_out); + + return git__percent_decode(local_path_out, file_url + offset); +} + +int git_path_walk_up( + git_buf *path, + const char *ceiling, + int (*cb)(void *data, git_buf *), + void *data) +{ + int error = 0; + git_buf iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + assert(path && cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_buf_len(path); + } + scan = git_buf_len(path); + + iter.ptr = path->ptr; + iter.size = git_buf_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + if ((error = cb(data, &iter)) < 0) + break; + iter.ptr[scan] = oldc; + scan = git_buf_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + return error; +} + +bool git_path_exists(const char *path) +{ + assert(path); + return p_access(path, F_OK) == 0; +} + +bool git_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; + + return S_ISDIR(st.st_mode) != 0; +} + +bool git_path_isfile(const char *path) +{ + struct stat st; + + assert(path); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +int git_path_lstat(const char *path, struct stat *st) +{ + int err = 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; +} + +static bool _check_dir_contents( + git_buf *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_buf_len(dir); + 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) < 0) + return false; + + /* save excursion */ + git_buf_joinpath(dir, dir->ptr, sub); + + result = predicate(dir->ptr); + + /* restore path */ + git_buf_truncate(dir, dir_size); + return result; +} + +bool git_path_contains(git_buf *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_path_exists); +} + +bool git_path_contains_dir(git_buf *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_path_isdir); +} + +bool git_path_contains_file(git_buf *base, const char *file) +{ + return _check_dir_contents(base, file, &git_path_isfile); +} + +int git_path_find_dir(git_buf *dir, const char *path, const char *base) +{ + int error; + + if (base != NULL && git_path_root(path) < 0) + error = git_buf_joinpath(dir, base, path); + else + error = git_buf_sets(dir, path); + + if (!error) { + char buf[GIT_PATH_MAX]; + if (p_realpath(dir->ptr, buf) != NULL) + error = git_buf_sets(dir, buf); + } + + /* call dirname if this is not a directory */ + if (!error && git_path_isdir(dir->ptr) == false) + error = git_path_dirname_r(dir, dir->ptr); + + if (!error) + error = git_path_to_dir(dir); + + return error; +} + +int git_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2) +{ + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = memcmp(name1, name2, len); + if (cmp) + return cmp; + if (len1 < len2) + return (!isdir1 && !isdir2) ? -1 : + (isdir1 ? '/' - name2[len1] : name2[len1] - '/'); + if (len1 > len2) + return (!isdir1 && !isdir2) ? 1 : + (isdir2 ? name1[len2] - '/' : '/' - name1[len2]); + return 0; +} + +/* Taken from git.git */ +GIT_INLINE(int) is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +int git_path_direach( + git_buf *path, + int (*fn)(void *, git_buf *), + void *arg) +{ + ssize_t wd_len; + DIR *dir; + struct dirent *de, *de_buf; + + if (git_path_to_dir(path) < 0) + return -1; + + wd_len = git_buf_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { + int result; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + if (git_buf_puts(path, de->d_name) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + + result = fn(arg, path); + + git_buf_truncate(path, wd_len); /* restore path */ + + if (result < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + return 0; +} + +int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents) +{ + int error, need_slash; + DIR *dir; + struct dirent *de, *de_buf; + size_t path_len; + + assert(path != NULL && contents != NULL); + path_len = strlen(path); + assert(path_len > 0 && path_len >= prefix_len); + + if ((dir = opendir(path)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + path += prefix_len; + path_len -= prefix_len; + 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; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + entry_len = strlen(de->d_name); + + entry_path = git__malloc( + path_len + need_slash + entry_len + 1 + alloc_extra); + GITERR_CHECK_ALLOC(entry_path); + + 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'; + + if (git_vector_insert(contents, entry_path) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + + if (error != 0) + giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); + + return error; +} + +int git_path_with_stat_cmp(const void *a, const void *b) +{ + const git_path_with_stat *psa = a, *psb = b; + return git__strcmp_cb(psa->path, psb->path); +} + +int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents) +{ + int error; + unsigned int i; + git_path_with_stat *ps; + git_buf full = GIT_BUF_INIT; + + if (git_buf_set(&full, path, prefix_len) < 0) + return -1; + + error = git_path_dirload( + path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + if (error < 0) { + git_buf_free(&full); + return error; + } + + git_vector_foreach(contents, i, ps) { + size_t path_len = strlen((char *)ps); + + memmove(ps->path, ps, path_len + 1); + ps->path_len = path_len; + + if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || + (error = git_path_lstat(full.ptr, &ps->st)) < 0) + break; + + git_buf_truncate(&full, prefix_len); + + if (S_ISDIR(ps->st.st_mode)) { + ps->path[path_len] = '/'; + ps->path[path_len + 1] = '\0'; + } + } + + git_buf_free(&full); + + return error; +} diff --git a/src/path.h b/src/path.h new file mode 100644 index 000000000..fd76805e5 --- /dev/null +++ b/src/path.h @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_path_h__ +#define INCLUDE_path_h__ + +#include "common.h" +#include "buffer.h" +#include "vector.h" + +/** + * Path manipulation utils + * + * These are path utilities that munge paths without actually + * looking at the real filesystem. + */ + +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git_path_dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_path_dirname_r` implementation writes the dirname to a `git_buf` + * if the buffer pointer is not NULL. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the dirname (which will be > 0). + */ +extern char *git_path_dirname(const char *path); +extern int git_path_dirname_r(git_buf *buffer, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git_path_basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_path_basename_r` implementation writes the basename to a `git_buf`. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the basename (which will be >= 0). + */ +extern char *git_path_basename(const char *path); +extern int git_path_basename_r(git_buf *buffer, const char *path); + +extern const char *git_path_topdir(const char *path); + +/** + * Find offset to root of path if path has one. + * + * This will return a number >= 0 which is the offset to the start of the + * path, if the path is rooted (i.e. "/rooted/path" returns 0 and + * "c:/windows/rooted/path" returns 2). If the path is not rooted, this + * returns < 0. + */ +extern int git_path_root(const char *path); + +/** + * Ensure path has a trailing '/'. + */ +extern int git_path_to_dir(git_buf *path); + +/** + * Ensure string has a trailing '/' if there is space for it. + */ +extern void git_path_string_to_dir(char* path, size_t size); + +#ifdef GIT_WIN32 +/** + * Convert backslashes in path to forward slashes. + */ +GIT_INLINE(void) git_path_mkposix(char *path) +{ + while (*path) { + if (*path == '\\') + *path = '/'; + + path++; + } +} +#else +# define git_path_mkposix(p) /* blank */ +#endif + +extern int git__percent_decode(git_buf *decoded_out, const char *input); + +/** + * Extract path from file:// URL. + */ +extern int git_path_fromurl(git_buf *local_path_out, const char *file_url); + + +/** + * Path filesystem utils + * + * These are path utilities that actually access the filesystem. + */ + +/** + * Check if a file exists and can be accessed. + * @return true or false + */ +extern bool git_path_exists(const char *path); + +/** + * Check if the given path points to a directory. + * @return true or false + */ +extern bool git_path_isdir(const char *path); + +/** + * Check if the given path points to a regular file. + * @return true or false + */ +extern bool git_path_isfile(const char *path); + +/** + * Stat a file and/or link and set error if needed. + */ +extern int git_path_lstat(const char *path, struct stat *st); + +/** + * Check if the parent directory contains the item. + * + * @param dir Directory to check. + * @param item Item that might be in the directory. + * @return 0 if item exists in directory, <0 otherwise. + */ +extern bool git_path_contains(git_buf *dir, const char *item); + +/** + * Check if the given path contains the given subdirectory. + * + * @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); + +/** + * Check if the given path contains the given file. + * + * @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); + +/** + * Clean up path, prepending base if it is not already rooted. + */ +extern int git_path_prettify(git_buf *path_out, const char *path, const char *base); + +/** + * Clean up path, prepending base if it is not already rooted and + * appending a slash. + */ +extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base); + +/** + * Get a directory from a path. + * + * If path is a directory, this acts like `git_path_prettify_dir` + * (cleaning up path and appending a '/'). If path is a normal file, + * this prettifies it, then removed the filename a la dirname and + * appends the trailing '/'. If the path does not exist, it is + * treated like a regular filename. + */ +extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); + +/** + * Walk each directory entry, except '.' and '..', calling fn(state). + * + * @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. + */ +extern int git_path_direach( + git_buf *pathbuf, + int (*fn)(void *, git_buf *), + void *state); + +/** + * Sort function to order two paths. + */ +extern int git_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2); + +/** + * Invoke callback up path directory by directory until the ceiling is + * reached (inclusive of a final call at the root_path). + * + * Returning anything other than 0 from the callback function + * will stop the iteration and propogate the error to the caller. + * + * @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. + * @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); + +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * 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 contents Vector to fill with directory entry names. + */ +extern int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents); + + +typedef struct { + struct stat st; + size_t path_len; + char path[GIT_FLEX_ARRAY]; +} git_path_with_stat; + +extern int git_path_with_stat_cmp(const void *a, const void *b); + +/** + * Load all directory entries along with stat info into a vector. + * + * This is just like git_path_dirload except that each entry in the + * vector is a git_path_with_stat structure that contains both the + * path and the stat info, plus directories will have a / suffixed + * to their path name. + */ +extern int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents); + +#endif diff --git a/src/pkt.c b/src/pkt.c new file mode 100644 index 000000000..88510f4b1 --- /dev/null +++ b/src/pkt.c @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/types.h" +#include "git2/errors.h" +#include "git2/refs.h" +#include "git2/revwalk.h" + +#include "pkt.h" +#include "util.h" +#include "netops.h" +#include "posix.h" +#include "buffer.h" + +#include + +#define PKT_LEN_SIZE 4 +static const char pkt_done_str[] = "0009done\n"; +static const char pkt_flush_str[] = "0000"; +static const char pkt_have_prefix[] = "0032have "; +static const char pkt_want_prefix[] = "0032want "; + +static int flush_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_FLUSH; + *out = pkt; + + return 0; +} + +/* the rest of the line will be useful for multi_ack */ +static int ack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt *pkt; + GIT_UNUSED(line); + GIT_UNUSED(len); + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ACK; + *out = pkt; + + return 0; +} + +static int nak_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_NAK; + *out = pkt; + + return 0; +} + +static int pack_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_PACK; + *out = pkt; + + return 0; +} + +static int comment_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_comment *pkt; + + pkt = git__malloc(sizeof(git_pkt_comment) + len + 1); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_COMMENT; + memcpy(pkt->comment, line, len); + pkt->comment[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; +} + +static int err_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt; + + /* Remove "ERR " from the line */ + line += 4; + len -= 4; + pkt = git__malloc(sizeof(git_pkt_err) + len + 1); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ERR; + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; +} + +/* + * Parse an other-ref line. + */ +static int ref_pkt(git_pkt **out, const char *line, size_t len) +{ + int error; + git_pkt_ref *pkt; + + pkt = git__malloc(sizeof(git_pkt_ref)); + GITERR_CHECK_ALLOC(pkt); + + memset(pkt, 0x0, sizeof(git_pkt_ref)); + pkt->type = GIT_PKT_REF; + if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0) + goto error_out; + + /* Check for a bit of consistency */ + if (line[GIT_OID_HEXSZ] != ' ') { + giterr_set(GITERR_NET, "Error parsing pkt-line"); + error = -1; + goto error_out; + } + + /* Jump from the name */ + line += GIT_OID_HEXSZ + 1; + len -= (GIT_OID_HEXSZ + 1); + + if (line[len - 1] == '\n') + --len; + + pkt->head.name = git__malloc(len + 1); + GITERR_CHECK_ALLOC(pkt->head.name); + + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; + + if (strlen(pkt->head.name) < len) { + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; + } + + *out = (git_pkt *)pkt; + return 0; + +error_out: + git__free(pkt); + return error; +} + +static int32_t parse_len(const char *line) +{ + char num[PKT_LEN_SIZE + 1]; + int i, error; + int32_t len; + const char *num_end; + + memcpy(num, line, PKT_LEN_SIZE); + num[PKT_LEN_SIZE] = '\0'; + + for (i = 0; i < PKT_LEN_SIZE; ++i) { + if (!isxdigit(num[i])) { + giterr_set(GITERR_NET, "Found invalid hex digit in length"); + return -1; + } + } + + if ((error = git__strtol32(&len, num, &num_end, 16)) < 0) + return error; + + return len; +} + +/* + * As per the documentation, the syntax is: + * + * pkt-line = data-pkt / flush-pkt + * data-pkt = pkt-len pkt-payload + * pkt-len = 4*(HEXDIG) + * pkt-payload = (pkt-len -4)*(OCTET) + * flush-pkt = "0000" + * + * Which means that the first four bytes are the length of the line, + * in ASCII hexadecimal (including itself) + */ + +int git_pkt_parse_line( + git_pkt **head, const char *line, const char **out, size_t bufflen) +{ + int ret; + int32_t len; + + /* Not even enough for the length */ + if (bufflen > 0 && bufflen < PKT_LEN_SIZE) + return GIT_EBUFS; + + len = parse_len(line); + if (len < 0) { + /* + * If we fail to parse the length, it might be because the + * server is trying to send us the packfile already. + */ + if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) { + giterr_clear(); + *out = line; + return pack_pkt(head); + } + + return (int)len; + } + + /* + * If we were given a buffer length, then make sure there is + * enough in the buffer to satisfy this line + */ + if (bufflen > 0 && bufflen < (size_t)len) + return GIT_EBUFS; + + line += PKT_LEN_SIZE; + /* + * TODO: How do we deal with empty lines? Try again? with the next + * line? + */ + if (len == PKT_LEN_SIZE) { + *out = line; + return 0; + } + + if (len == 0) { /* Flush pkt */ + *out = line; + return flush_pkt(head); + } + + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ + + /* Assming the minimal size is actually 4 */ + if (!git__prefixcmp(line, "ACK")) + ret = ack_pkt(head, line, len); + else if (!git__prefixcmp(line, "NAK")) + ret = nak_pkt(head); + else if (!git__prefixcmp(line, "ERR ")) + ret = err_pkt(head, line, len); + else if (*line == '#') + ret = comment_pkt(head, line, len); + else + ret = ref_pkt(head, line, len); + + *out = line + len; + + return ret; +} + +void git_pkt_free(git_pkt *pkt) +{ + if (pkt->type == GIT_PKT_REF) { + git_pkt_ref *p = (git_pkt_ref *) pkt; + git__free(p->head.name); + } + + git__free(pkt); +} + +int git_pkt_buffer_flush(git_buf *buf) +{ + return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); +} + +static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) +{ + char capstr[20]; + char oid[GIT_OID_HEXSZ +1] = {0}; + unsigned int len; + + if (caps->ofs_delta) + strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr)); + + len = (unsigned int) + (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + + strlen(capstr) + 1 /* LF */); + git_buf_grow(buf, git_buf_len(buf) + len); + + git_oid_fmt(oid, &head->oid); + return git_buf_printf(buf, "%04xwant %s%c%s\n", len, oid, 0, capstr); +} + +/* + * All "want" packets have the same length and format, so what we do + * is overwrite the OID each time. + */ + +int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf) +{ + unsigned int i = 0; + git_remote_head *head; + + if (caps->common) { + for (; i < refs->length; ++i) { + head = refs->contents[i]; + if (!head->local) + break; + } + + if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0) + return -1; + + i++; + } + + for (; i < refs->length; ++i) { + char oid[GIT_OID_HEXSZ]; + + head = refs->contents[i]; + if (head->local) + continue; + + git_oid_fmt(oid, &head->oid); + git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); + git_buf_put(buf, oid, GIT_OID_HEXSZ); + git_buf_putc(buf, '\n'); + if (git_buf_oom(buf)) + return -1; + } + + return git_pkt_buffer_flush(buf); +} + +int git_pkt_buffer_have(git_oid *oid, git_buf *buf) +{ + char oidhex[GIT_OID_HEXSZ + 1]; + + memset(oidhex, 0x0, sizeof(oidhex)); + git_oid_fmt(oidhex, oid); + return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); +} + +int git_pkt_buffer_done(git_buf *buf) +{ + return git_buf_puts(buf, pkt_done_str); +} diff --git a/src/pkt.h b/src/pkt.h new file mode 100644 index 000000000..75442c833 --- /dev/null +++ b/src/pkt.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_pkt_h__ +#define INCLUDE_pkt_h__ + +#include "common.h" +#include "transport.h" +#include "buffer.h" +#include "posix.h" +#include "git2/net.h" + +enum git_pkt_type { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_PACK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, +}; + +/* Used for multi-ack */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + enum git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + enum git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + enum git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + enum git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + enum git_pkt_type type; + char comment[GIT_FLEX_ARRAY]; +} git_pkt_comment; + +typedef struct { + enum git_pkt_type type; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + +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); +int git_pkt_send_flush(GIT_SOCKET s); +int git_pkt_buffer_done(git_buf *buf); +int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf); +int git_pkt_buffer_have(git_oid *oid, git_buf *buf); +void git_pkt_free(git_pkt *pkt); + +#endif diff --git a/src/pool.c b/src/pool.c new file mode 100644 index 000000000..641292d06 --- /dev/null +++ b/src/pool.c @@ -0,0 +1,294 @@ +#include "pool.h" +#ifndef GIT_WIN32 +#include +#endif + +struct git_pool_page { + git_pool_page *next; + uint32_t size; + uint32_t avail; + char data[GIT_FLEX_ARRAY]; +}; + +#define GIT_POOL_MIN_USABLE 4 +#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*) + +static void *pool_alloc_page(git_pool *pool, uint32_t size); +static void pool_insert_page(git_pool *pool, git_pool_page *page); + +int git_pool_init( + git_pool *pool, uint32_t item_size, uint32_t items_per_page) +{ + assert(pool); + + if (!item_size) + item_size = 1; + /* round up item_size for decent object alignment */ + if (item_size > 4) + item_size = (item_size + 7) & ~7; + else if (item_size == 3) + item_size = 4; + + if (!items_per_page) + items_per_page = git_pool__suggest_items_per_page(item_size); + if (item_size * items_per_page < GIT_POOL_MIN_PAGESZ) + items_per_page = (GIT_POOL_MIN_PAGESZ + item_size - 1) / item_size; + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = item_size * items_per_page; + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_pool_page *scan, *next; + + for (scan = pool->open; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + pool->open = NULL; + + for (scan = pool->full; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + pool->full = NULL; + + pool->free_list = NULL; + + pool->items = 0; + + pool->has_string_alloc = 0; + pool->has_multi_item_alloc = 0; + pool->has_large_page_alloc = 0; +} + +void git_pool_swap(git_pool *a, git_pool *b) +{ + git_pool temp; + + if (a == b) + return; + + memcpy(&temp, a, sizeof(temp)); + memcpy(a, b, sizeof(temp)); + memcpy(b, &temp, sizeof(temp)); +} + +static void pool_insert_page(git_pool *pool, git_pool_page *page) +{ + git_pool_page *scan; + + /* If there are no open pages or this page has the most open space, + * insert it at the beginning of the list. This is the common case. + */ + if (pool->open == NULL || pool->open->avail < page->avail) { + page->next = pool->open; + pool->open = page; + return; + } + + /* Otherwise insert into sorted position. */ + for (scan = pool->open; + scan->next && scan->next->avail > page->avail; + scan = scan->next); + page->next = scan->next; + scan->next = page; +} + +static void *pool_alloc_page(git_pool *pool, uint32_t size) +{ + git_pool_page *page; + uint32_t alloc_size; + + if (size <= pool->page_size) + alloc_size = pool->page_size; + else { + alloc_size = size; + pool->has_large_page_alloc = 1; + } + + page = git__calloc(1, alloc_size + sizeof(git_pool_page)); + if (!page) + return NULL; + + page->size = alloc_size; + page->avail = alloc_size - size; + + if (page->avail > 0) + pool_insert_page(pool, page); + else { + page->next = pool->full; + pool->full = page; + } + + pool->items++; + + return page->data; +} + +GIT_INLINE(void) pool_remove_page( + git_pool *pool, git_pool_page *page, git_pool_page *prev) +{ + if (prev == NULL) + pool->open = page->next; + else + prev->next = page->next; +} + +void *git_pool_malloc(git_pool *pool, uint32_t items) +{ + git_pool_page *scan = pool->open, *prev; + uint32_t size = items * pool->item_size; + void *ptr = NULL; + + pool->has_string_alloc = 0; + if (items > 1) + pool->has_multi_item_alloc = 1; + else if (pool->free_list != NULL) { + ptr = pool->free_list; + pool->free_list = *((void **)pool->free_list); + return ptr; + } + + /* just add a block if there is no open one to accomodate this */ + if (size >= pool->page_size || !scan || scan->avail < size) + return pool_alloc_page(pool, size); + + pool->items++; + + /* find smallest block in free list with space */ + for (scan = pool->open, prev = NULL; + scan->next && scan->next->avail >= size; + prev = scan, scan = scan->next); + + /* allocate space from the block */ + ptr = &scan->data[scan->size - scan->avail]; + scan->avail -= size; + + /* move to full list if there is almost no space left */ + if (scan->avail < pool->item_size || scan->avail < GIT_POOL_MIN_USABLE) { + pool_remove_page(pool, scan, prev); + scan->next = pool->full; + pool->full = scan; + } + /* reorder list if block is now smaller than the one after it */ + else if (scan->next != NULL && scan->next->avail > scan->avail) { + pool_remove_page(pool, scan, prev); + pool_insert_page(pool, scan); + } + + return ptr; +} + +char *git_pool_strndup(git_pool *pool, const char *str, size_t n) +{ + void *ptr = NULL; + + assert(pool && str && pool->item_size == sizeof(char)); + + if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) { + memcpy(ptr, str, n); + *(((char *)ptr) + n) = '\0'; + } + pool->has_string_alloc = 1; + + return ptr; +} + +char *git_pool_strdup(git_pool *pool, const char *str) +{ + assert(pool && str && pool->item_size == sizeof(char)); + + return git_pool_strndup(pool, str, strlen(str)); +} + +char *git_pool_strcat(git_pool *pool, const char *a, const char *b) +{ + void *ptr; + size_t len_a, len_b; + + assert(pool && a && b && pool->item_size == sizeof(char)); + + len_a = a ? strlen(a) : 0; + len_b = b ? strlen(b) : 0; + + if ((ptr = git_pool_malloc(pool, (uint32_t)(len_a + len_b + 1))) != NULL) { + if (len_a) + memcpy(ptr, a, len_a); + if (len_b) + memcpy(((char *)ptr) + len_a, b, len_b); + *(((char *)ptr) + len_a + len_b) = '\0'; + } + pool->has_string_alloc = 1; + + return ptr; +} + +void git_pool_free(git_pool *pool, void *ptr) +{ + assert(pool && ptr && pool->item_size >= sizeof(void*)); + + *((void **)ptr) = pool->free_list; + pool->free_list = ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->open; scan != NULL; scan = scan->next) ct++; + return ct; +} + +uint32_t git_pool__full_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->full; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->open; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + for (scan = pool->full; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +uint32_t git_pool__system_page_size(void) +{ + static uint32_t size = 0; + + if (!size) { +#ifdef GIT_WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + size = (uint32_t)info.dwPageSize; +#else + size = (uint32_t)sysconf(_SC_PAGE_SIZE); +#endif + + size -= 2 * sizeof(void *); /* allow space for malloc overhead */ + } + + return size; +} + +uint32_t git_pool__suggest_items_per_page(uint32_t item_size) +{ + uint32_t page_bytes = + git_pool__system_page_size() - sizeof(git_pool_page); + return page_bytes / item_size; +} + diff --git a/src/pool.h b/src/pool.h new file mode 100644 index 000000000..54a2861ed --- /dev/null +++ b/src/pool.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_pool_h__ +#define INCLUDE_pool_h__ + +#include "common.h" + +typedef struct git_pool_page git_pool_page; + +/** + * Chunked allocator. + * + * A `git_pool` can be used when you want to cheaply allocate + * multiple items of the same type and are willing to free them + * all together with a single call. The two most common cases + * are a set of fixed size items (such as lots of OIDs) or a + * bunch of strings. + * + * Internally, a `git_pool` allocates pages of memory and then + * deals out blocks from the trailing unused portion of each page. + * The pages guarantee that the number of actual allocations done + * will be much smaller than the number of items needed. + * + * For examples of how to set up a `git_pool` see `git_pool_init`. + */ +typedef struct { + git_pool_page *open; /* pages with space left */ + git_pool_page *full; /* pages with no space left */ + void *free_list; /* optional: list of freed blocks */ + uint32_t item_size; /* size of single alloc unit in bytes */ + uint32_t page_size; /* size of page in bytes */ + uint32_t items; + unsigned has_string_alloc : 1; /* was the strdup function used */ + unsigned has_multi_item_alloc : 1; /* was items ever > 1 in malloc */ + unsigned has_large_page_alloc : 1; /* are any pages > page_size */ +} git_pool; + +#define GIT_POOL_INIT_STRINGPOOL { 0, 0, 0, 1, 4000, 0, 0, 0, 0 } + +/** + * Initialize a pool. + * + * To allocation strings, use like this: + * + * git_pool_init(&string_pool, 1, 0); + * my_string = git_pool_strdup(&string_pool, your_string); + * + * To allocate items of fixed size, use like this: + * + * git_pool_init(&pool, sizeof(item), 0); + * my_item = git_pool_malloc(&pool, 1); + * + * Of course, you can use this in other ways, but those are the + * two most common patterns. + */ +extern int git_pool_init( + git_pool *pool, uint32_t item_size, uint32_t items_per_page); + +/** + * Free all items in pool + */ +extern void git_pool_clear(git_pool *pool); + +/** + * Swap two pools with one another + */ +extern void git_pool_swap(git_pool *a, git_pool *b); + +/** + * Allocate space for one or more items from a pool. + */ +extern void *git_pool_malloc(git_pool *pool, uint32_t items); + +/** + * Allocate space and duplicate string data into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); + +/** + * Allocate space and duplicate a string into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup(git_pool *pool, const char *str); + +/** + * Allocate space for the concatenation of two strings. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); + +/** + * Push a block back onto the free list for the pool. + * + * This is allowed only if the item_size is >= sizeof(void*). + * + * In some cases, it is helpful to "release" an allocated block + * for reuse. Pools don't support a general purpose free, but + * they will keep a simple free blocks linked list provided the + * native block size is large enough to hold a void pointer + */ +extern void git_pool_free(git_pool *pool, void *ptr); + +/* + * Misc utilities + */ + +extern uint32_t git_pool__open_pages(git_pool *pool); + +extern uint32_t git_pool__full_pages(git_pool *pool); + +extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); + +extern uint32_t git_pool__system_page_size(void); + +extern uint32_t git_pool__suggest_items_per_page(uint32_t item_size); + +#endif diff --git a/src/posix.c b/src/posix.c new file mode 100644 index 000000000..a9a6af984 --- /dev/null +++ b/src/posix.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "posix.h" +#include "path.h" +#include +#include + +#ifndef GIT_WIN32 + +int p_open(const char *path, int flags, ...) +{ + mode_t mode = 0; + + if (flags & O_CREAT) + { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + return open(path, flags | O_BINARY, mode); +} + +int p_creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); +} + +int p_getcwd(char *buffer_out, size_t size) +{ + char *cwd_buffer; + + assert(buffer_out && size > 0); + + cwd_buffer = getcwd(buffer_out, size); + + if (cwd_buffer == NULL) + return -1; + + git_path_mkposix(buffer_out); + git_path_string_to_dir(buffer_out, size); /* append trailing slash */ + + return 0; +} + +int p_rename(const char *from, const char *to) +{ + if (!link(from, to)) { + p_unlink(from); + return 0; + } + + if (!rename(from, to)) + return 0; + + return -1; +} + +#endif + +int p_read(git_file fd, void *buf, size_t cnt) +{ + char *b = buf; + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + assert((size_t)((unsigned int)cnt) == cnt); + r = read(fd, b, (unsigned int)cnt); +#else + r = read(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!r) + break; + cnt -= r; + b += r; + } + return (int)(b - (char *)buf); +} + +int p_write(git_file fd, const void *buf, size_t cnt) +{ + const char *b = buf; + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + assert((size_t)((unsigned int)cnt) == cnt); + r = write(fd, b, (unsigned int)cnt); +#else + r = write(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!r) { + errno = EPIPE; + return -1; + } + cnt -= r; + b += r; + } + return 0; +} diff --git a/src/posix.h b/src/posix.h new file mode 100644 index 000000000..d020d94ac --- /dev/null +++ b/src/posix.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_posix_h__ +#define INCLUDE_posix_h__ + +#include "common.h" +#include +#include + +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) + +#if !defined(O_BINARY) +#define O_BINARY 0 +#endif + +typedef int git_file; + +/** + * Standard POSIX Methods + * + * All the methods starting with the `p_` prefix are + * direct ports of the standard POSIX methods. + * + * Some of the methods are slightly wrapped to provide + * saner defaults. Some of these methods are emulated + * in Windows platforns. + * + * Use your manpages to check the docs on these. + * Straightforward + */ + +extern int p_read(git_file fd, void *buf, size_t cnt); +extern int p_write(git_file fd, const void *buf, size_t cnt); + +#define p_fstat(f,b) fstat(f, b) +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_close(fd) close(fd) +#define p_umask(m) umask(m) + +extern int p_open(const char *path, int flags, ...); +extern int p_creat(const char *path, mode_t mode); +extern int p_getcwd(char *buffer_out, size_t size); +extern int p_rename(const char *from, const char *to); + +#ifndef GIT_WIN32 + +#define p_stat(p,b) stat(p, b) +#define p_chdir(p) chdir(p) +#define p_rmdir(p) rmdir(p) +#define p_chmod(p,m) chmod(p, m) +#define p_access(p,m) access(p,m) +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#else + +typedef SOCKET GIT_SOCKET; + +#endif + +/** + * Platform-dependent methods + */ +#ifdef GIT_WIN32 +# include "win32/posix.h" +#else +# include "unix/posix.h" +#endif + +#define p_readdir_r(d,e,r) readdir_r(d,e,r) + +#endif diff --git a/src/ppc/sha1.c b/src/ppc/sha1.c index ec6a1926d..803b81d0a 100644 --- a/src/ppc/sha1.c +++ b/src/ppc/sha1.c @@ -1,17 +1,15 @@ /* - * SHA-1 implementation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * Copyright (C) 2005 Paul Mackerras - * - * This version assumes we are running on a big-endian machine. - * It calls an external sha1_core() to process blocks of 64 bytes. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include #include #include "sha1.h" extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p, - unsigned int nblocks); + unsigned int nblocks); int ppc_SHA1_Init(ppc_SHA_CTX *c) { diff --git a/src/ppc/sha1.h b/src/ppc/sha1.h index 70957110c..aca4e5dda 100644 --- a/src/ppc/sha1.h +++ b/src/ppc/sha1.h @@ -1,7 +1,8 @@ /* - * SHA-1 implementation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * Copyright (C) 2005 Paul Mackerras + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include diff --git a/src/pqueue.c b/src/pqueue.c index 6307175e3..cb59c13ec 100644 --- a/src/pqueue.c +++ b/src/pqueue.c @@ -1,52 +1,36 @@ /* - * BORING COPYRIGHT NOTICE: + * Copyright (C) 2009-2012 the libgit2 contributors * - * This file is a heavily modified version of the priority queue found - * in the Apache project and the libpqueue library. - * - * https://github.com/vy/libpqueue - * - * These are the original authors: - * - * Copyright 2010 Volkan Yazıcı - * Copyright 2006-2010 The Apache Software Foundation - * - * This file is licensed under the Apache 2.0 license, which - * supposedly makes it compatible with the GPLv2 that libgit2 uses. - * - * Check the Apache license at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * So much licensing trouble for a binary heap. Oh well. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "pqueue.h" -#define left(i) ((i) << 1) -#define right(i) (((i) << 1) + 1) +#define left(i) ((i) << 1) +#define right(i) (((i) << 1) + 1) #define parent(i) ((i) >> 1) int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri) { assert(q); - /* Need to allocate n+1 elements since element 0 isn't used. */ - if ((q->d = malloc((n + 1) * sizeof(void *))) == NULL) - return GIT_ENOMEM; + /* Need to allocate n+1 elements since element 0 isn't used. */ + q->d = git__malloc((n + 1) * sizeof(void *)); + GITERR_CHECK_ALLOC(q->d); - q->size = 1; - q->avail = q->step = (n + 1); /* see comment above about n+1 */ - q->cmppri = cmppri; + q->size = 1; + q->avail = q->step = (n + 1); /* see comment above about n+1 */ + q->cmppri = cmppri; - return GIT_SUCCESS; + return 0; } void git_pqueue_free(git_pqueue *q) { - free(q->d); + git__free(q->d); q->d = NULL; } @@ -57,101 +41,101 @@ void git_pqueue_clear(git_pqueue *q) size_t git_pqueue_size(git_pqueue *q) { - /* queue element 0 exists but doesn't count since it isn't used. */ - return (q->size - 1); + /* queue element 0 exists but doesn't count since it isn't used. */ + return (q->size - 1); } static void bubble_up(git_pqueue *q, size_t i) { - size_t parent_node; - void *moving_node = q->d[i]; + size_t parent_node; + void *moving_node = q->d[i]; - for (parent_node = parent(i); - ((i > 1) && q->cmppri(q->d[parent_node], moving_node)); - i = parent_node, parent_node = parent(i)) { - q->d[i] = q->d[parent_node]; - } + for (parent_node = parent(i); + ((i > 1) && q->cmppri(q->d[parent_node], moving_node)); + i = parent_node, parent_node = parent(i)) { + q->d[i] = q->d[parent_node]; + } - q->d[i] = moving_node; + q->d[i] = moving_node; } static size_t maxchild(git_pqueue *q, size_t i) { - size_t child_node = left(i); + size_t child_node = left(i); - if (child_node >= q->size) - return 0; + if (child_node >= q->size) + return 0; - if ((child_node + 1) < q->size && - q->cmppri(q->d[child_node], q->d[child_node + 1])) - child_node++; /* use right child instead of left */ + if ((child_node + 1) < q->size && + q->cmppri(q->d[child_node], q->d[child_node + 1])) + child_node++; /* use right child instead of left */ - return child_node; + return child_node; } static void percolate_down(git_pqueue *q, size_t i) { - size_t child_node; - void *moving_node = q->d[i]; + size_t child_node; + void *moving_node = q->d[i]; - while ((child_node = maxchild(q, i)) != 0 && - q->cmppri(moving_node, q->d[child_node])) { - q->d[i] = q->d[child_node]; - i = child_node; - } + while ((child_node = maxchild(q, i)) != 0 && + q->cmppri(moving_node, q->d[child_node])) { + q->d[i] = q->d[child_node]; + i = child_node; + } - q->d[i] = moving_node; + q->d[i] = moving_node; } int git_pqueue_insert(git_pqueue *q, void *d) { - void *tmp; - size_t i; - size_t newsize; + void *tmp; + size_t i; + size_t newsize; - if (!q) return 1; + if (!q) return 1; - /* allocate more memory if necessary */ - if (q->size >= q->avail) { - newsize = q->size + q->step; - if ((tmp = realloc(q->d, sizeof(void *) * newsize)) == NULL) - return GIT_ENOMEM; + /* allocate more memory if necessary */ + if (q->size >= q->avail) { + newsize = q->size + q->step; + tmp = git__realloc(q->d, sizeof(void *) * newsize); + GITERR_CHECK_ALLOC(tmp); - q->d = tmp; - q->avail = newsize; - } + q->d = tmp; + q->avail = newsize; + } - /* insert item */ - i = q->size++; - q->d[i] = d; - bubble_up(q, i); + /* insert item */ + i = q->size++; + q->d[i] = d; + bubble_up(q, i); - return GIT_SUCCESS; + return 0; } void *git_pqueue_pop(git_pqueue *q) { - void *head; + void *head; - if (!q || q->size == 1) - return NULL; + if (!q || q->size == 1) + return NULL; - head = q->d[1]; - q->d[1] = q->d[--q->size]; - percolate_down(q, 1); + head = q->d[1]; + q->d[1] = q->d[--q->size]; + percolate_down(q, 1); - return head; + return head; } void *git_pqueue_peek(git_pqueue *q) { - if (!q || q->size == 1) - return NULL; - return q->d[1]; + if (!q || q->size == 1) + return NULL; + return q->d[1]; } diff --git a/src/pqueue.h b/src/pqueue.h index 7a1394803..a3e1edd1d 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -1,24 +1,8 @@ /* - * BORING COPYRIGHT NOTICE: + * Copyright (C) 2009-2012 the libgit2 contributors * - * This file is a heavily modified version of the priority queue found - * in the Apache project and the libpqueue library. - * - * https://github.com/vy/libpqueue - * - * These are the original authors: - * - * Copyright 2010 Volkan Yazıcı - * Copyright 2006-2010 The Apache Software Foundation - * - * This file is licensed under the Apache 2.0 license, which - * supposedly makes it compatible with the GPLv2 that libgit2 uses. - * - * Check the Apache license at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * So much licensing trouble for a binary heap. Oh well. + * 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_pqueue_h__ @@ -29,9 +13,9 @@ typedef int (*git_pqueue_cmp)(void *a, void *b); /** the priority queue handle */ typedef struct { - size_t size, avail, step; - git_pqueue_cmp cmppri; - void **d; + size_t size, avail, step; + git_pqueue_cmp cmppri; + void **d; } git_pqueue; @@ -39,7 +23,7 @@ typedef struct { * initialize the queue * * @param n the initial estimate of the number of queue items for which memory - * should be preallocated + * should be preallocated * @param cmppri the callback function to compare two nodes of the queue * * @Return the handle or NULL for insufficent memory diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 000000000..6b3861796 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "protocol.h" +#include "pkt.h" +#include "buffer.h" + +int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) +{ + git_buf *buf = &p->buf; + git_vector *refs = p->refs; + int error; + const char *line_end, *ptr; + + if (len == 0) { /* EOF */ + if (git_buf_len(buf) != 0) { + giterr_set(GITERR_NET, "Unexpected EOF"); + return p->error = -1; + } else { + return 0; + } + } + + git_buf_put(buf, data, len); + ptr = buf->ptr; + while (1) { + git_pkt *pkt; + + if (git_buf_len(buf) == 0) + return 0; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); + if (error == GIT_EBUFS) + return 0; /* Ask for more */ + if (error < 0) + return p->error = -1; + + git_buf_consume(buf, line_end); + + if (pkt->type == GIT_PKT_ERR) { + giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (git_vector_insert(refs, pkt) < 0) + return p->error = -1; + + if (pkt->type == GIT_PKT_FLUSH) + p->flush = 1; + } + + return 0; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 000000000..a6c3e0735 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_protocol_h__ +#define INCLUDE_protocol_h__ + +#include "transport.h" +#include "buffer.h" + +typedef struct { + git_transport *transport; + git_vector *refs; + git_buf buf; + int error; + unsigned int flush :1; +} git_protocol; + +int git_protocol_store_refs(git_protocol *p, const char *data, size_t len); + +#endif diff --git a/src/reflog.c b/src/reflog.c new file mode 100644 index 000000000..3ea073e65 --- /dev/null +++ b/src/reflog.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "reflog.h" +#include "repository.h" +#include "filebuf.h" +#include "signature.h" + +static int reflog_init(git_reflog **reflog, git_reference *ref) +{ + 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; + } + + *reflog = log; + + return 0; +} + +static int reflog_write(const char *log_path, const char *oid_old, + const char *oid_new, const git_signature *committer, + const char *msg) +{ + int error; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + bool trailing_newline = false; + + assert(log_path && oid_old && oid_new && committer); + + if (msg) { + const char *newline = strchr(msg, '\n'); + if (newline) { + if (*(newline + 1) == '\0') + trailing_newline = true; + else { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + return -1; + } + } + } + + git_buf_puts(&log, oid_old); + git_buf_putc(&log, ' '); + + git_buf_puts(&log, oid_new); + + git_signature__writebuf(&log, " ", committer); + git_buf_truncate(&log, log.size - 1); /* drop LF */ + + if (msg) { + git_buf_putc(&log, '\t'); + git_buf_puts(&log, msg); + } + + if (!trailing_newline) + git_buf_putc(&log, '\n'); + + if (git_buf_oom(&log)) { + git_buf_free(&log); + return -1; + } + + error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); + if (!error) { + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + git_filebuf_cleanup(&fbuf); + else + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + } + + git_buf_free(&log); + + return error; +} + +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__malloc(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__free(entry->committer); + git__free(entry); + } + return -1; +} + +void git_reflog_free(git_reflog *reflog) +{ + unsigned int i; + git_reflog_entry *entry; + + for (i=0; i < reflog->entries.length; i++) { + entry = git_vector_get(&reflog->entries, i); + + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); + } + + git_vector_free(&reflog->entries); + git__free(reflog->ref_name); + git__free(reflog); +} + +int git_reflog_read(git_reflog **reflog, git_reference *ref) +{ + int error; + git_buf log_path = GIT_BUF_INIT; + git_buf log_file = GIT_BUF_INIT; + git_reflog *log = NULL; + + *reflog = NULL; + + if (reflog_init(&log, ref) < 0) + return -1; + + error = git_buf_join_n(&log_path, '/', 3, + ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + + if (!error) + error = git_futils_readbuffer(&log_file, log_path.ptr); + + if (!error) + error = reflog_parse(log, log_file.ptr, log_file.size); + + if (!error) + *reflog = log; + else + git_reflog_free(log); + + git_buf_free(&log_file); + git_buf_free(&log_path); + + return error; +} + +int git_reflog_write(git_reference *ref, const git_oid *oid_old, + const git_signature *committer, const char *msg) +{ + int error; + char old[GIT_OID_HEXSZ+1]; + char new[GIT_OID_HEXSZ+1]; + git_buf log_path = GIT_BUF_INIT; + git_reference *r; + const git_oid *oid; + + if ((error = git_reference_resolve(&r, ref)) < 0) + return error; + + oid = git_reference_oid(r); + if (oid == NULL) { + giterr_set(GITERR_REFERENCE, + "Failed to write reflog. Cannot resolve reference `%s`", r->name); + git_reference_free(r); + return -1; + } + + git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); + + git_reference_free(r); + + error = git_buf_join_n(&log_path, '/', 3, + ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + if (error < 0) + goto cleanup; + + if (git_path_exists(log_path.ptr) == false) { + error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); + } else if (git_path_isfile(log_path.ptr) == false) { + giterr_set(GITERR_REFERENCE, + "Failed to write reflog. `%s` is directory", log_path.ptr); + error = -1; + } else if (oid_old == NULL) { + giterr_set(GITERR_REFERENCE, + "Failed to write reflog. Old OID cannot be NULL for existing reference"); + error = -1; + } + if (error < 0) + goto cleanup; + + if (oid_old) + git_oid_tostr(old, sizeof(old), oid_old); + else + p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0); + + error = reflog_write(log_path.ptr, old, new, committer, msg); + +cleanup: + git_buf_free(&log_path); + return error; +} + +int git_reflog_rename(git_reference *ref, const char *new_name) +{ + int error; + git_buf old_path = GIT_BUF_INIT; + git_buf new_path = GIT_BUF_INIT; + + if (!git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository, + GIT_REFLOG_DIR, ref->name) && + !git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository, + GIT_REFLOG_DIR, new_name)) + error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path)); + else + error = -1; + + git_buf_free(&old_path); + git_buf_free(&new_path); + + return error; +} + +int git_reflog_delete(git_reference *ref) +{ + int error; + git_buf path = GIT_BUF_INIT; + + error = git_buf_join_n( + &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + + if (!error && git_path_exists(path.ptr)) + error = p_unlink(path.ptr); + + git_buf_free(&path); + + return error; +} + +unsigned int git_reflog_entrycount(git_reflog *reflog) +{ + assert(reflog); + return reflog->entries.length; +} + +const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx) +{ + assert(reflog); + return git_vector_get(&reflog->entries, idx); +} + +const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry) +{ + assert(entry); + return &entry->oid_old; +} + +const git_oid * git_reflog_entry_oidnew(const git_reflog_entry *entry) +{ + assert(entry); + return &entry->oid_cur; +} + +git_signature * git_reflog_entry_committer(const git_reflog_entry *entry) +{ + assert(entry); + return entry->committer; +} + +char * git_reflog_entry_msg(const git_reflog_entry *entry) +{ + assert(entry); + return entry->msg; +} diff --git a/src/reflog.h b/src/reflog.h new file mode 100644 index 000000000..33cf0776c --- /dev/null +++ b/src/reflog.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_reflog_h__ +#define INCLUDE_reflog_h__ + +#include "common.h" +#include "git2/reflog.h" +#include "vector.h" + +#define GIT_REFLOG_DIR "logs/" +#define GIT_REFLOG_DIR_MODE 0777 +#define GIT_REFLOG_FILE_MODE 0666 + +#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) + +struct git_reflog_entry { + git_oid oid_old; + git_oid oid_cur; + + git_signature *committer; + + char *msg; +}; + +struct git_reflog { + char *ref_name; + git_vector entries; +}; + +#endif /* INCLUDE_reflog_h__ */ diff --git a/src/refs.c b/src/refs.c index c21c9583d..1ef3e13a4 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1,433 +1,332 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "refs.h" #include "hash.h" #include "repository.h" #include "fileops.h" +#include "pack.h" +#include "reflog.h" #include #include -#define MAX_NESTING_LEVEL 5 +GIT__USE_STRMAP; -typedef struct { - git_reference ref; +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; + +struct packref { git_oid oid; - git_oid peel_target; -} reference_oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; -typedef struct { - git_reference ref; - char *target; -} reference_symbolic; - -static const int default_table_size = 32; - -static uint32_t reftable_hash(const void *key, int hash_id) -{ - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; - - return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); -} - -static void reference_free(git_reference *reference); -static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type); -static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name); +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated); /* loose refs */ -static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content); -static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content); -static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic); +static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); +static int loose_parse_oid(git_oid *ref, git_buf *file_content); +static int loose_lookup(git_reference *ref); +static int loose_lookup_to_packfile(struct packref **ref_out, + git_repository *repo, const char *name); static int loose_write(git_reference *ref); -static int loose_update(git_reference *ref); /* packed refs */ -static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end); -static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end); +static int packed_parse_peel(struct packref *tag_ref, + const char **buffer_out, const char *buffer_end); +static int packed_parse_oid(struct packref **ref_out, + const char **buffer_out, const char *buffer_end); static int packed_load(git_repository *repo); static int packed_loadloose(git_repository *repository); -static int packed_write_ref(reference_oid *ref, git_filebuf *file); -static int packed_find_peel(reference_oid *ref); +static int packed_write_ref(struct packref *ref, git_filebuf *file); +static int packed_find_peel(git_repository *repo, struct packref *ref); static int packed_remove_loose(git_repository *repo, git_vector *packing_list); static int packed_sort(const void *a, const void *b); +static int packed_lookup(git_reference *ref); static int packed_write(git_repository *repo); /* internal helpers */ -static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force); -static int reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force); -static int reference_rename(git_reference *ref, const char *new_name, int force); +static int reference_path_available(git_repository *repo, + const char *ref, const char *old_ref); +static int reference_delete(git_reference *ref); +static int reference_lookup(git_reference *ref); /* name normalization */ -static int check_valid_ref_char(char ch); -static int normalize_name(char *buffer_out, const char *name, int is_oid_ref); +static int normalize_name(char *buffer_out, size_t out_size, + const char *name, int is_oid_ref); -/***************************************** - * Internal methods - Constructor/destructor - *****************************************/ -static void reference_free(git_reference *reference) + +void git_reference_free(git_reference *reference) { if (reference == NULL) return; - if (reference->name) - free(reference->name); + git__free(reference->name); + reference->name = NULL; - if (reference->type == GIT_REF_SYMBOLIC) - free(((reference_symbolic *)reference)->target); + if (reference->flags & GIT_REF_SYMBOLIC) { + git__free(reference->target.symbolic); + reference->target.symbolic = NULL; + } - free(reference); + git__free(reference); } -static int reference_create( +static int reference_alloc( git_reference **ref_out, git_repository *repo, - const char *name, - git_rtype type) + const char *name) { - char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - int error = GIT_SUCCESS, size; git_reference *reference = NULL; assert(ref_out && repo && name); - if (type == GIT_REF_SYMBOLIC) - size = sizeof(reference_symbolic); - else if (type == GIT_REF_OID) - size = sizeof(reference_oid); - else - return git__throw(GIT_EINVALIDARGS, - "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); + reference = git__malloc(sizeof(git_reference)); + GITERR_CHECK_ALLOC(reference); - reference = git__malloc(size); - if (reference == NULL) - return GIT_ENOMEM; - - memset(reference, 0x0, size); + memset(reference, 0x0, sizeof(git_reference)); reference->owner = repo; - reference->type = type; - error = normalize_name(normalized, name, (type & GIT_REF_OID)); - if (error < GIT_SUCCESS) - goto cleanup; - - reference->name = git__strdup(normalized); - if (reference->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + reference->name = git__strdup(name); + GITERR_CHECK_ALLOC(reference->name); *ref_out = reference; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); - -cleanup: - reference_free(reference); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); + return 0; } -static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name) +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated) { - struct stat st; - char path[GIT_PATH_MAX]; + git_buf path = GIT_BUF_INIT; + int result; + + assert(file_content && repo_path && ref_name); /* Determine the full path of the file */ - git__joinpath(path, repo_path, ref_name); + if (git_buf_joinpath(&path, repo_path, ref_name) < 0) + return -1; - if (gitfo_stat(path, &st) < 0 || S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, - "Cannot read reference file '%s'", ref_name); - - if (mtime) - *mtime = st.st_mtime; - - if (file_content) - return gitfo_read_file(file_content, path); - - return GIT_SUCCESS; + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); + git_buf_free(&path); + return result; } - - - -/***************************************** - * Internal methods - Loose references - *****************************************/ -static int loose_update(git_reference *ref) +static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) { - int error; - time_t ref_time; - gitfo_buf ref_file = GITFO_BUF_INIT; - - if (ref->type & GIT_REF_PACKED) - return packed_load(ref->owner); - - error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref_time == ref->mtime) - return GIT_SUCCESS; - - error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref->type == GIT_REF_SYMBOLIC) - error = loose_parse_symbolic(ref, &ref_file); - else if (ref->type == GIT_REF_OID) - error = loose_parse_oid(ref, &ref_file); - else - error = git__throw(GIT_EOBJCORRUPTED, - "Invalid reference type (%d) for loose reference", ref->type); - - gitfo_free_buf(&ref_file); - -cleanup: - if (error != GIT_SUCCESS) { - reference_free(ref); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - } - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference"); -} - -static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) -{ - const unsigned int header_len = strlen(GIT_SYMREF); + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); const char *refname_start; char *eol; - reference_symbolic *ref_sym; - refname_start = (const char *)file_content->data; - ref_sym = (reference_symbolic *)ref; + refname_start = (const char *)file_content->ptr; - if (file_content->len < (header_len + 1)) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Object too short"); + if (git_buf_len(file_content) < header_len + 1) + goto corrupt; - /* + /* * Assume we have already checked for the header - * before calling this function + * before calling this function */ - refname_start += header_len; - free(ref_sym->target); - ref_sym->target = git__strdup(refname_start); - if (ref_sym->target == NULL) - return GIT_ENOMEM; + ref->target.symbolic = git__strdup(refname_start); + GITERR_CHECK_ALLOC(ref->target.symbolic); /* remove newline at the end of file */ - eol = strchr(ref_sym->target, '\n'); + eol = strchr(ref->target.symbolic, '\n'); if (eol == NULL) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Missing EOL"); + goto corrupt; *eol = '\0'; if (eol[-1] == '\r') eol[-1] = '\0'; - return GIT_SUCCESS; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; } -static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) +static int loose_parse_oid(git_oid *oid, git_buf *file_content) { - int error; - reference_oid *ref_oid; char *buffer; - buffer = (char *)file_content->data; - ref_oid = (reference_oid *)ref; + buffer = (char *)file_content->ptr; /* File format: 40 chars (OID) + newline */ - if (file_content->len < GIT_OID_HEXSZ + 1) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Reference too short"); + if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1) + goto corrupt; - if ((error = git_oid_mkstr(&ref_oid->oid, buffer)) < GIT_SUCCESS) - return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); + if (git_oid_fromstr(oid, buffer) < 0) + goto corrupt; buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Missing EOL"); + goto corrupt; - return GIT_SUCCESS; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; } - -static git_rtype loose_guess_rtype(const char *full_path) +static git_ref_t loose_guess_rtype(const git_buf *full_path) { - gitfo_buf ref_file = GITFO_BUF_INIT; - git_rtype type; + git_buf ref_file = GIT_BUF_INIT; + git_ref_t type; type = GIT_REF_INVALID; - if (gitfo_read_file(&ref_file, full_path) == GIT_SUCCESS) { - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) + if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) type = GIT_REF_SYMBOLIC; else type = GIT_REF_OID; } - gitfo_free_buf(&ref_file); + git_buf_free(&ref_file); return type; } -static int loose_lookup( - git_reference **ref_out, - git_repository *repo, - const char *name, - int skip_symbolic) +static int loose_lookup(git_reference *ref) { - int error = GIT_SUCCESS; - gitfo_buf ref_file = GITFO_BUF_INIT; - git_reference *ref = NULL; - time_t ref_time; + int result, updated; + git_buf ref_file = GIT_BUF_INIT; + + result = reference_read(&ref_file, &ref->mtime, + ref->owner->path_repository, ref->name, &updated); + + if (result < 0) + return result; + + if (!updated) + return 0; + + if (ref->flags & GIT_REF_SYMBOLIC) { + git__free(ref->target.symbolic); + ref->target.symbolic = NULL; + } + + ref->flags = 0; + + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { + ref->flags |= GIT_REF_SYMBOLIC; + result = loose_parse_symbolic(ref, &ref_file); + } else { + ref->flags |= GIT_REF_OID; + result = loose_parse_oid(&ref->target.oid, &ref_file); + } + + git_buf_free(&ref_file); + return result; +} + +static int loose_lookup_to_packfile( + struct packref **ref_out, + git_repository *repo, + const char *name) +{ + git_buf ref_file = GIT_BUF_INIT; + struct packref *ref = NULL; + size_t name_len; *ref_out = NULL; - error = reference_read(&ref_file, &ref_time, repo->path_repository, name); - if (error < GIT_SUCCESS) - goto cleanup; + if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) + return -1; - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { - if (skip_symbolic) - return GIT_SUCCESS; + name_len = strlen(name); + ref = git__malloc(sizeof(struct packref) + name_len + 1); + GITERR_CHECK_ALLOC(ref); - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; + memcpy(ref->name, name, name_len); + ref->name[name_len] = 0; - error = loose_parse_symbolic(ref, &ref_file); - } else { - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; - - error = loose_parse_oid(ref, &ref_file); + if (loose_parse_oid(&ref->oid, &ref_file) < 0) { + git_buf_free(&ref_file); + git__free(ref); + return -1; } - if (error < GIT_SUCCESS) - goto cleanup; + ref->flags = GIT_PACKREF_WAS_LOOSE; - ref->mtime = ref_time; *ref_out = ref; - gitfo_free_buf(&ref_file); - return GIT_SUCCESS; - -cleanup: - gitfo_free_buf(&ref_file); - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference"); + git_buf_free(&ref_file); + return 0; } static int loose_write(git_reference *ref) { - git_filebuf file; - char ref_path[GIT_PATH_MAX]; - int error, contents_size; - char *ref_contents = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + git_buf ref_path = GIT_BUF_INIT; struct stat st; - assert((ref->type & GIT_REF_PACKED) == 0); + if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) + return -1; - git__joinpath(ref_path, ref->owner->path_repository, ref->name); - - if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose reference"); - - if (ref->type & GIT_REF_OID) { - reference_oid *ref_oid = (reference_oid *)ref; - - contents_size = GIT_OID_HEXSZ + 1; - ref_contents = git__malloc(contents_size); - if (ref_contents == NULL) { - error = GIT_ENOMEM; - goto unlock; + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if (git_path_isdir(git_buf_cstr(&ref_path)) && + (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) { + git_buf_free(&ref_path); + return -1; } - git_oid_fmt(ref_contents, &ref_oid->oid); - - } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ - reference_symbolic *ref_sym = (reference_symbolic *)ref; - - contents_size = strlen(GIT_SYMREF) + strlen(ref_sym->target) + 1; - ref_contents = git__malloc(contents_size); - if (ref_contents == NULL) { - error = GIT_ENOMEM; - goto unlock; - } - - strcpy(ref_contents, GIT_SYMREF); - strcat(ref_contents, ref_sym->target); - } else { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type"); - goto unlock; + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&ref_path); + return -1; } - /* TODO: win32 carriage return when writing references in Windows? */ - ref_contents[contents_size - 1] = '\n'; + git_buf_free(&ref_path); - if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS) - goto unlock; + if (ref->flags & GIT_REF_OID) { + char oid[GIT_OID_HEXSZ + 1]; - error = git_filebuf_commit(&file); + git_oid_fmt(oid, &ref->target.oid); + oid[GIT_OID_HEXSZ] = '\0'; - if (gitfo_stat(ref_path, &st) == GIT_SUCCESS) + git_filebuf_printf(&file, "%s\n", oid); + + } else if (ref->flags & GIT_REF_SYMBOLIC) { + git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else { + assert(0); /* don't let this happen */ + } + + if (p_stat(ref_path.ptr, &st) == 0) ref->mtime = st.st_mtime; - free(ref_contents); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); - -unlock: - git_filebuf_cleanup(&file); - free(ref_contents); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); + return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); } - - - - - -/***************************************** - * Internal methods - Packed references - *****************************************/ - static int packed_parse_peel( - reference_oid *tag_ref, - const char **buffer_out, + struct packref *tag_ref, + const char **buffer_out, const char *buffer_end) { const char *buffer = *buffer_out + 1; @@ -436,185 +335,164 @@ static int packed_parse_peel( /* Ensure it's not the first entry of the file */ if (tag_ref == NULL) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file"); + goto corrupt; /* Ensure reference is a tag */ - if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag"); + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) + goto corrupt; if (buffer + GIT_OID_HEXSZ >= buffer_end) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small"); + goto corrupt; /* Is this a valid object id? */ - if (git_oid_mkstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not 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 != '\n') - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly"); + goto corrupt; *buffer_out = buffer + 1; - tag_ref->ref.type |= GIT_REF_HAS_PEEL; + return 0; - return GIT_SUCCESS; +corrupt: + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_parse_oid( - reference_oid **ref_out, - git_repository *repo, + struct packref **ref_out, const char **buffer_out, const char *buffer_end) { - reference_oid *ref = NULL; + struct packref *ref = NULL; const char *buffer = *buffer_out; const char *refname_begin, *refname_end; - int error = GIT_SUCCESS; - int refname_len; - char refname[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + size_t refname_len; git_oid id; refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || - refname_begin[-1] != ' ') { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (refname_begin >= buffer_end || refname_begin[-1] != ' ') + goto corrupt; /* Is this a valid object id? */ - if ((error = git_oid_mkstr(&id, buffer)) < GIT_SUCCESS) - goto cleanup; + if (git_oid_fromstr(&id, buffer) < 0) + goto corrupt; refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (refname_end == NULL) + goto corrupt; + + if (refname_end[-1] == '\r') + refname_end--; refname_len = refname_end - refname_begin; - memcpy(refname, refname_begin, refname_len); - refname[refname_len] = 0; + ref = git__malloc(sizeof(struct packref) + refname_len + 1); + GITERR_CHECK_ALLOC(ref); - if (refname[refname_len - 1] == '\r') - refname[refname_len - 1] = 0; - - error = reference_create((git_reference **)&ref, repo, refname, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; git_oid_cpy(&ref->oid, &id); - ref->ref.type |= GIT_REF_PACKED; + + ref->flags = 0; *ref_out = ref; *buffer_out = refname_end + 1; - return GIT_SUCCESS; + return 0; -cleanup: - reference_free((git_reference *)ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference"); +corrupt: + git__free(ref); + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_load(git_repository *repo) { - int error = GIT_SUCCESS; - gitfo_buf packfile = GITFO_BUF_INIT; + int result, updated; + git_buf packfile = GIT_BUF_INIT; const char *buffer_start, *buffer_end; git_refcache *ref_cache = &repo->references; - /* already loaded */ - if (repo->references.packfile != NULL) { - time_t packed_time; - - /* check if we can read the time of the index; - * if we can read it and it matches the time of the - * index we had previously loaded, we don't need to do - * anything else. - * - * if we cannot load the time (e.g. the packfile - * has disappeared) or the time is different, we - * have to reload the packfile */ - - if (!reference_read(NULL, &packed_time, repo->path_repository, GIT_PACKEDREFS_FILE) && - packed_time == ref_cache->packfile_time) - return GIT_SUCCESS; - - git_hashtable_clear(repo->references.packfile); - } else { - ref_cache->packfile = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)strcmp); - - if (ref_cache->packfile == NULL) - return GIT_ENOMEM; + /* 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); } - /* read the packfile from disk; - * store its modification time to check for future reloads */ - error = reference_read( - &packfile, - &ref_cache->packfile_time, - repo->path_repository, - GIT_PACKEDREFS_FILE); + result = reference_read(&packfile, &ref_cache->packfile_time, + repo->path_repository, GIT_PACKEDREFS_FILE, &updated); - /* there is no packfile on disk; that's ok */ - if (error == GIT_ENOTFOUND) - return GIT_SUCCESS; + /* + * 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 (result == GIT_ENOTFOUND) { + git_strmap_clear(ref_cache->packfile); + return 0; + } - if (error < GIT_SUCCESS) - goto cleanup; + if (result < 0) + return -1; - buffer_start = (const char *)packfile.data; - buffer_end = (const char *)(buffer_start) + packfile.len; + if (!updated) + return 0; + + /* + * 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; while (buffer_start < buffer_end && buffer_start[0] == '#') { buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (buffer_start == NULL) + goto parse_failed; + buffer_start++; } while (buffer_start < buffer_end) { - reference_oid *ref = NULL; + int err; + struct packref *ref = NULL; - error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end); - if (error < GIT_SUCCESS) - goto cleanup; + if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + goto parse_failed; if (buffer_start[0] == '^') { - error = packed_parse_peel(ref, &buffer_start, buffer_end); - if (error < GIT_SUCCESS) - goto cleanup; + if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + goto parse_failed; } - error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref); - if (error < GIT_SUCCESS) { - reference_free((git_reference *)ref); - goto cleanup; - } + git_strmap_insert(ref_cache->packfile, ref->name, ref, err); + if (err < 0) + goto parse_failed; } - gitfo_free_buf(&packfile); - return GIT_SUCCESS; + git_buf_free(&packfile); + return 0; -cleanup: - git_hashtable_free(ref_cache->packfile); +parse_failed: + git_strmap_free(ref_cache->packfile); ref_cache->packfile = NULL; - gitfo_free_buf(&packfile); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references"); + git_buf_free(&packfile); + return -1; } - - struct dirent_list_data { git_repository *repo; size_t repo_path_len; @@ -624,52 +502,52 @@ struct dirent_list_data { void *callback_payload; }; -static int _dirent_loose_listall(void *_data, char *full_path) +static int _dirent_loose_listall(void *_data, git_buf *full_path) { struct dirent_list_data *data = (struct dirent_list_data *)_data; - char *file_path = full_path + data->repo_path_len; + const char *file_path = full_path->ptr + data->repo_path_len; - if (gitfo_isdir(full_path) == GIT_SUCCESS) - return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data); + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_listall, _data); /* do not add twice a reference that exists already in the packfile */ if ((data->list_flags & GIT_REF_PACKED) != 0 && - git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL) - return GIT_SUCCESS; + git_strmap_exists(data->repo->references.packfile, file_path)) + return 0; if (data->list_flags != GIT_REF_LISTALL) { if ((data->list_flags & loose_guess_rtype(full_path)) == 0) - return GIT_SUCCESS; /* we are filtering out this reference */ + return 0; /* we are filtering out this reference */ } return data->callback(file_path, data->callback_payload); } -static int _dirent_loose_load(void *data, char *full_path) +static int _dirent_loose_load(void *data, git_buf *full_path) { git_repository *repository = (git_repository *)data; - git_reference *reference, *old_ref; - char *file_path; - int error; + void *old_ref = NULL; + struct packref *ref; + const char *file_path; + int err; - if (gitfo_isdir(full_path) == GIT_SUCCESS) - return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_load, repository); + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_load, repository); - file_path = full_path + strlen(repository->path_repository); - error = loose_lookup(&reference, repository, file_path, 1); - if (error == GIT_SUCCESS && reference != NULL) { - reference->type |= GIT_REF_PACKED; + file_path = full_path->ptr + strlen(repository->path_repository); - if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, (void **)&old_ref) < GIT_SUCCESS) { - reference_free(reference); - return GIT_ENOMEM; - } + if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) + return -1; - if (old_ref != NULL) - reference_free(old_ref); + git_strmap_insert2( + repository->references.packfile, ref->name, ref, old_ref, err); + if (err < 0) { + git__free(ref); + return -1; } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent"); + git__free(old_ref); + return 0; } /* @@ -680,45 +558,37 @@ static int _dirent_loose_load(void *data, char *full_path) */ static int packed_loadloose(git_repository *repository) { - char refs_path[GIT_PATH_MAX]; + git_buf refs_path = GIT_BUF_INIT; + int result; /* the packfile must have been previously loaded! */ assert(repository->references.packfile); - git__joinpath(refs_path, repository->path_repository, GIT_REFS_DIR); - - /* Remove any loose references from the cache */ - { - const void *GIT_UNUSED(_unused); - git_reference *reference; - - GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, - reference_free(reference); - ); - } - - git_hashtable_clear(repository->references.loose_cache); + if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0) + return -1; /* * Load all the loose files from disk into the Packfile table. * This will overwrite any old packed entries with their - * updated loose versions + * updated loose versions */ - return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository); + result = git_path_direach(&refs_path, _dirent_loose_load, repository); + git_buf_free(&refs_path); + + return result; } /* * Write a single reference into a packfile */ -static int packed_write_ref(reference_oid *ref, git_filebuf *file) +static int packed_write_ref(struct packref *ref, git_filebuf *file) { - int error; char oid[GIT_OID_HEXSZ + 1]; git_oid_fmt(oid, &ref->oid); oid[GIT_OID_HEXSZ] = 0; - /* + /* * For references that peel to an object in the repo, we must * write the resulting peel on a separate line, e.g. * @@ -728,48 +598,48 @@ static int packed_write_ref(reference_oid *ref, git_filebuf *file) * This obviously only applies to tags. * The required peels have already been loaded into `ref->peel_target`. */ - if (ref->ref.type & GIT_REF_HAS_PEEL) { + if (ref->flags & GIT_PACKREF_HAS_PEEL) { char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel_target); + git_oid_fmt(peel, &ref->peel); peel[GIT_OID_HEXSZ] = 0; - error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel); + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; } else { - error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name); + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); + return 0; } /* * Find out what object this reference resolves to. * - * For references that point to a 'big' tag (e.g. an + * For references that point to a 'big' tag (e.g. an * actual tag object on the repository), we need to * cache on the packfile the OID of the object to * which that 'big tag' is pointing to. */ -static int packed_find_peel(reference_oid *ref) +static int packed_find_peel(git_repository *repo, struct packref *ref) { git_object *object; - int error; - if (ref->ref.type & GIT_REF_HAS_PEEL) - return GIT_SUCCESS; + if (ref->flags & GIT_PACKREF_HAS_PEEL) + return 0; /* * Only applies to tags, i.e. references * in the /refs/tags folder */ - if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0) - return GIT_SUCCESS; + if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) + return 0; /* * Find the tagged object in the repository */ - error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY); - if (error < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference"); + if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0) + return -1; /* * If the tagged object is a Tag object, we need to resolve it; @@ -782,8 +652,8 @@ static int packed_find_peel(reference_oid *ref) /* * Find the object pointed at by this tag */ - git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); - ref->ref.type |= GIT_REF_HAS_PEEL; + git_oid_cpy(&ref->peel, git_tag_target_oid(tag)); + ref->flags |= GIT_PACKREF_HAS_PEEL; /* * The reference has now cached the resolved OID, and is @@ -792,9 +662,8 @@ static int packed_find_peel(reference_oid *ref) */ } - git_object_close(object); - - return GIT_SUCCESS; + git_object_free(object); + return 0; } /* @@ -811,44 +680,45 @@ static int packed_find_peel(reference_oid *ref) static int packed_remove_loose(git_repository *repo, git_vector *packing_list) { unsigned int i; - char full_path[GIT_PATH_MAX]; - int error = GIT_SUCCESS; - git_reference *reference; + git_buf full_path = GIT_BUF_INIT; + int failed = 0; for (i = 0; i < packing_list->length; ++i) { - git_reference *ref = git_vector_get(packing_list, i); + struct packref *ref = git_vector_get(packing_list, i); - /* Ensure the packed reference doesn't exist - * in a (more up-to-date?) state as a loose reference - */ - reference = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name); - if (reference != NULL) + if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) continue; - git__joinpath(full_path, repo->path_repository, ref->name); + if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) + return -1; /* critical; do not try to recover on oom */ - if (gitfo_exists(full_path) == GIT_SUCCESS && - gitfo_unlink(full_path) < GIT_SUCCESS) - error = GIT_EOSERR; + if (git_path_exists(full_path.ptr) == true && 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; + } /* * if we fail to remove a single file, this is *not* good, * but we should keep going and remove as many as possible. * After we've removed as many files as possible, we return * the error code anyway. - * - * TODO: mark this with a very special error code? - * GIT_EFAILTORMLOOSE */ } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference"); + git_buf_free(&full_path); + return failed ? -1 : 0; } static int packed_sort(const void *a, const void *b) { - const git_reference *ref_a = *(const git_reference **)a; - const git_reference *ref_b = *(const git_reference **)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); } @@ -858,408 +728,450 @@ static int packed_sort(const void *a, const void *b) */ static int packed_write(git_repository *repo) { - git_filebuf pack_file; - int error; + git_filebuf pack_file = GIT_FILEBUF_INIT; unsigned int i; - char pack_file_path[GIT_PATH_MAX]; - + git_buf pack_file_path = GIT_BUF_INIT; git_vector packing_list; - size_t total_refs; + unsigned int total_refs; assert(repo && repo->references.packfile); - total_refs = repo->references.packfile->key_count; - if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write packed reference"); + total_refs = + (unsigned int)git_strmap_num_entries(repo->references.packfile); + + if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) + return -1; /* Load all the packfile into a vector */ { - git_reference *reference; - const void *GIT_UNUSED(_unused); + struct packref *reference; - GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, - git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */ - ); + /* cannot fail: vector already has the right size */ + git_strmap_foreach_value(repo->references.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! */ - git__joinpath(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE); - if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write packed reference"); + if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) + goto cleanup_memory; + + if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) + goto cleanup_packfile; /* Packfiles have a header... apparently * This is in fact not required, but we might as well print it * just for kicks */ - if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write packed reference"); + if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + goto cleanup_packfile; for (i = 0; i < packing_list.length; ++i) { - reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i); + struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - /* only direct references go to the packfile; otherwise - * this is a disaster */ - assert(ref->ref.type & GIT_REF_OID); + if (packed_find_peel(repo, ref) < 0) + goto cleanup_packfile; - if ((error = packed_find_peel(ref)) < GIT_SUCCESS) - goto cleanup; - - if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS) - goto cleanup; + if (packed_write_ref(ref, &pack_file) < 0) + goto cleanup_packfile; } -cleanup: /* if we've written all the references properly, we can commit * the packfile to make the changes effective */ - if (error == GIT_SUCCESS) { - error = git_filebuf_commit(&pack_file); + if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) + goto cleanup_memory; - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if (error == GIT_SUCCESS) { - struct stat st; + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if (packed_remove_loose(repo, &packing_list) < 0) + goto cleanup_memory; - error = packed_remove_loose(repo, &packing_list); - - if (gitfo_stat(pack_file_path, &st) == GIT_SUCCESS) - repo->references.packfile_time = st.st_mtime; - } - } - else git_filebuf_cleanup(&pack_file); + { + struct stat st; + if (p_stat(pack_file_path.ptr, &st) == 0) + repo->references.packfile_time = st.st_mtime; + } git_vector_free(&packing_list); + git_buf_free(&pack_file_path); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); + /* we're good now */ + return 0; + +cleanup_packfile: + git_filebuf_cleanup(&pack_file); + +cleanup_memory: + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); + + return -1; } -/***************************************** - * Internal methods - reference creation - *****************************************/ +struct reference_available_t { + const char *new_ref; + const char *old_ref; + int available; +}; -static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force) +static int _reference_available_cb(const char *ref, void *data) { - char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - int error = GIT_SUCCESS, updated = 0; - git_reference *ref = NULL, *old_ref = NULL; + struct reference_available_t *d; - if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists"); + assert(ref && data); + d = (struct reference_available_t *)data; - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref->type & GIT_REF_SYMBOLIC){ - updated = 1; - } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; + if (!d->old_ref || strcmp(d->old_ref, ref)) { + size_t reflen = strlen(ref); + size_t newlen = strlen(d->new_ref); + size_t cmplen = reflen < newlen ? reflen : newlen; + const char *lead = reflen < newlen ? d->new_ref : ref; + + if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') { + d->available = 0; + return -1; + } } - /* The target can aither be the name of an object id reference or the name of another symbolic reference */ - error = normalize_name(normalized, target, 0); - if (error < GIT_SUCCESS) - goto cleanup; - - /* set the target; this will write the reference on disk */ - error = git_reference_set_target(ref, normalized); - if (error < GIT_SUCCESS) - goto cleanup; - - /* - * If we didn't update the ref, then we need to insert or replace - * it in the loose cache. If we replaced a ref, free it. - */ - if (!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; - - if(old_ref) - reference_free(old_ref); - } - - *ref_out = ref; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); - -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); + return 0; } -static int reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) +static int reference_path_available( + git_repository *repo, + const char *ref, + const char* old_ref) { - int error = GIT_SUCCESS, updated = 0; - git_reference *ref = NULL, *old_ref = NULL; + struct reference_available_t data; - if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists"); + data.new_ref = ref; + data.old_ref = old_ref; + data.available = 1; - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref-> type & GIT_REF_OID){ - updated = 1; + if (git_reference_foreach(repo, GIT_REF_LISTALL, + _reference_available_cb, (void *)&data) < 0) + return -1; + + if (!data.available) { + giterr_set(GITERR_REFERENCE, + "The path to reference '%s' collides with an existing one", ref); + return -1; + } + + return 0; +} + +static int reference_exists(int *exists, git_repository *repo, const char *ref_name) +{ + git_buf ref_path = GIT_BUF_INIT; + + if (packed_load(repo) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) + return -1; + + if (git_path_isfile(ref_path.ptr) == true || + git_strmap_exists(repo->references.packfile, ref_path.ptr)) + { + *exists = 1; } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; + *exists = 0; } - /* set the oid; this will write the reference on disk */ - error = git_reference_set_oid(ref, id); - if (error < GIT_SUCCESS) - goto cleanup; - - if(!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; - - if(old_ref) - reference_free(old_ref); - } - - *ref_out = ref; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); - -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); + git_buf_free(&ref_path); + return 0; } /* - * Rename a reference + * Check if a reference could be written to disk, based on: * - * If the reference is packed, we need to rewrite the - * packfile to remove the reference from it and create - * the reference back as a loose one. + * - Whether a reference with the same name already exists, + * and we are allowing or disallowing overwrites * - * If the reference is loose, we just rename it on - * the filesystem. - * - * We also need to re-insert the reference on its corresponding - * in-memory cache, since the caches are indexed by refname. + * - Whether the name of the reference would collide with + * an existing path */ -static int reference_rename(git_reference *ref, const char *new_name, int force) +static int reference_can_write( + git_repository *repo, + const char *refname, + const char *previous_name, + int force) { - int error; - char *old_name; - char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - git_reference *looked_up_ref, *old_ref = NULL; + /* see if the reference shares a path with an existing reference; + * if a path is shared, we cannot create the reference, even when forcing */ + if (reference_path_available(repo, refname, previous_name) < 0) + return -1; + + /* check if the reference actually exists, but only if we are not forcing + * the rename. If we are forcing, it's OK to overwrite */ + if (!force) { + int exists; + + if (reference_exists(&exists, repo, refname) < 0) + return -1; + + /* We cannot proceed if the reference already exists and we're not forcing + * the rename; the existing one would be overwritten */ + if (exists) { + giterr_set(GITERR_REFERENCE, + "A reference with that name (%s) already exists", refname); + return GIT_EEXISTS; + } + } + + /* FIXME: if the reference exists and we are forcing, do we really need to + * remove the reference first? + * + * Two cases: + * + * - the reference already exists and is loose: not a problem, the file + * gets overwritten on disk + * + * - the reference already exists and is packed: we write a new one as + * loose, which by all means renders the packed one useless + */ + + return 0; +} + + +static int packed_lookup(git_reference *ref) +{ + struct packref *pack_ref = NULL; + git_strmap *packfile_refs; + khiter_t pos; + + if (packed_load(ref->owner) < 0) + return -1; + + /* maybe the packfile hasn't changed at all, so we don't + * have to re-lookup the reference */ + if ((ref->flags & GIT_REF_PACKED) && + ref->mtime == ref->owner->references.packfile_time) + return 0; + + if (ref->flags & GIT_REF_SYMBOLIC) { + git__free(ref->target.symbolic); + ref->target.symbolic = NULL; + } + + /* Look up on the packfile */ + packfile_refs = ref->owner->references.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; + } + + pack_ref = git_strmap_value_at(packfile_refs, pos); + + ref->flags = GIT_REF_OID | GIT_REF_PACKED; + ref->mtime = ref->owner->references.packfile_time; + git_oid_cpy(&ref->target.oid, &pack_ref->oid); + + return 0; +} + +static int reference_lookup(git_reference *ref) +{ + int result; + + result = loose_lookup(ref); + if (result == 0) + 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) { + giterr_clear(); + result = packed_lookup(ref); + if (result == 0) + return 0; + } + + /* unexpected error; free the reference */ + git_reference_free(ref); + return result; +} + +/* + * Delete a reference. + * This is an internal method; the reference is removed + * from disk or the packfile, but the pointer is not freed + */ +static int reference_delete(git_reference *ref) +{ + int result; assert(ref); - /* Ensure the name is valid */ - error = normalize_name(normalized_name, new_name, ref->type & GIT_REF_OID); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to rename reference"); + /* If the reference is packed, this is an expensive operation. + * We need to reload the packfile, remove the reference from the + * packing list, and repack */ + if (ref->flags & GIT_REF_PACKED) { + git_strmap *packfile_refs; + struct packref *packref; + khiter_t pos; - /* Ensure we're not going to overwrite an existing reference - unless the user has allowed us */ - error = git_reference_lookup(&looked_up_ref, ref->owner, new_name); - if (error == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists"); + /* load the existing packfile */ + if (packed_load(ref->owner) < 0) + return -1; - if (error < GIT_SUCCESS && - error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to rename reference"); - - - old_name = ref->name; - ref->name = git__strdup(new_name); - - if (ref->name == NULL) { - ref->name = old_name; - return GIT_ENOMEM; - } - - if (ref->type & GIT_REF_PACKED) { - /* write the packfile to disk; note - * that the state of the in-memory cache is not - * consistent, because the reference is indexed - * by its old name but it already has the new one. - * This doesn't affect writing, though, and allows - * us to rollback if writing fails - */ - - ref->type &= ~GIT_REF_PACKED; - - /* Create the loose ref under its new name */ - error = loose_write(ref); - if (error < GIT_SUCCESS) { - ref->type |= GIT_REF_PACKED; - goto cleanup; + packfile_refs = ref->owner->references.packfile; + pos = git_strmap_lookup_index(packfile_refs, ref->name); + if (!git_strmap_valid_index(packfile_refs, pos)) { + giterr_set(GITERR_REFERENCE, + "Reference %s stopped existing in the packfile", ref->name); + return -1; } - /* Remove from the packfile cache in order to avoid packing it back - * Note : we do not rely on git_reference_delete() because this would - * invalidate the reference. - */ - git_hashtable_remove(ref->owner->references.packfile, old_name); + packref = git_strmap_value_at(packfile_refs, pos); + git_strmap_delete_at(packfile_refs, pos); - /* Recreate the packed-refs file without the reference */ - error = packed_write(ref->owner); - if (error < GIT_SUCCESS) - goto rename_loose_to_old_name; + git__free(packref); + if (packed_write(ref->owner) < 0) + return -1; + /* If the reference is loose, we can just remove the reference + * from the filesystem */ } else { - git__joinpath(old_path, ref->owner->path_repository, old_name); - git__joinpath(new_path, ref->owner->path_repository, ref->name); + git_reference *ref_in_pack; + git_buf full_path = GIT_BUF_INIT; - error = gitfo_mv_force(old_path, new_path); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) + return -1; - /* Once succesfully renamed, remove from the cache the reference known by its old name*/ - git_hashtable_remove(ref->owner->references.loose_cache, old_name); + result = p_unlink(full_path.ptr); + git_buf_free(&full_path); /* done with path at this point */ + + if (result < 0) { + giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); + return -1; + } + + /* When deleting a loose reference, we have to ensure that an older + * packed version of it doesn't exist */ + if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { + assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); + return git_reference_delete(ref_in_pack); + } + + giterr_clear(); } - /* Store the renamed reference into the loose ref cache */ - error = git_hashtable_insert2(ref->owner->references.loose_cache, ref->name, ref, (void **) &old_ref); - - /* If we force-replaced, we need to free the old reference */ - if(old_ref) - reference_free(old_ref); - - free(old_name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); - -cleanup: - /* restore the old name if this failed */ - free(ref->name); - ref->name = old_name; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); - -rename_loose_to_old_name: - /* If we hit this point. Something *bad* happened! Think "Ghostbusters - * crossing the streams" definition of bad. - * Either the packed-refs has been correctly generated and something else - * has gone wrong, or the writing of the new packed-refs has failed, and - * we're stuck with the old one. As a loose ref always takes priority over - * a packed ref, we'll eventually try and rename the generated loose ref to - * its former name. It even that fails, well... we might have lost the reference - * for good. :-/ - */ - - git__joinpath(old_path, ref->owner->path_repository, ref->name); - git__joinpath(new_path, ref->owner->path_repository, old_name); - - /* No error checking. We'll return the initial error */ - gitfo_mv_force(old_path, new_path); - - /* restore the old name */ - free(ref->name); - ref->name = old_name; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); + return 0; } -/***************************************** - * External Library API - *****************************************/ +int git_reference_delete(git_reference *ref) +{ + int result = reference_delete(ref); + git_reference_free(ref); + return result; +} -/** - * Constructors - */ -int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name) +int git_reference_lookup(git_reference **ref_out, + git_repository *repo, const char *name) +{ + return git_reference_lookup_resolved(ref_out, repo, name, 0); +} + +int git_reference_name_to_oid( + git_oid *out, git_repository *repo, const char *name) { int error; - char normalized_name[GIT_PATH_MAX]; + git_reference *ref; + + if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) + return error; + + git_oid_cpy(out, git_reference_oid(ref)); + git_reference_free(ref); + return 0; +} + +int git_reference_lookup_resolved( + git_reference **ref_out, + git_repository *repo, + const char *name, + int max_nesting) +{ + git_reference *scan; + int result, nesting; assert(ref_out && repo && name); *ref_out = NULL; - error = normalize_name(normalized_name, name, 0); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); + if (max_nesting > MAX_NESTING_LEVEL) + max_nesting = MAX_NESTING_LEVEL; + else if (max_nesting < 0) + max_nesting = DEFAULT_NESTING_LEVEL; - /* First, check has been previously loaded and cached */ - *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name); - if (*ref_out != NULL) - return loose_update(*ref_out); + scan = git__calloc(1, sizeof(git_reference)); + GITERR_CHECK_ALLOC(scan); - /* Then check if there is a loose file for that reference.*/ - error = loose_lookup(ref_out, repo, normalized_name, 0); + scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); + GITERR_CHECK_ALLOC(scan->name); - /* If the file exists, we store it on the cache */ - if (error == GIT_SUCCESS) - return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out)); + if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) { + git_reference_free(scan); + return result; + } - /* The loose lookup has failed, but not because the reference wasn't found; - * probably the loose reference is corrupted. this is bad. */ - if (error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to lookup reference"); + scan->target.symbolic = git__strdup(scan->name); + GITERR_CHECK_ALLOC(scan->target.symbolic); - /* - * If we cannot find a loose reference, we look into the packfile - * Load the packfile first if it hasn't been loaded - */ - /* load all the packed references */ - error = packed_load(repo); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); + scan->owner = repo; + scan->flags = GIT_REF_SYMBOLIC; - /* Look up on the packfile */ - *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name); - if (*ref_out != NULL) - return GIT_SUCCESS; + for (nesting = max_nesting; + nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0; + nesting--) + { + if (nesting != max_nesting) + strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX); - /* The reference doesn't exist anywhere */ - return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist"); -} + scan->mtime = 0; -int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target) -{ - return reference_create_symbolic(ref_out, repo, name, target, 0); -} + if ((result = reference_lookup(scan)) < 0) + return result; /* lookup git_reference_free on scan already */ + } -int git_reference_create_symbolic_f(git_reference **ref_out, git_repository *repo, const char *name, const char *target) -{ - return reference_create_symbolic(ref_out, repo, name, target, 1); -} + if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) { + giterr_set(GITERR_REFERENCE, + "Cannot resolve reference (>%u levels deep)", max_nesting); + git_reference_free(scan); + return -1; + } -int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) -{ - return reference_create_oid(ref_out, repo, name, id, 0); -} - -int git_reference_create_oid_f(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) -{ - return reference_create_oid(ref_out, repo, name, id, 1); + *ref_out = scan; + return 0; } /** * Getters */ -git_rtype git_reference_type(git_reference *ref) +git_ref_t git_reference_type(git_reference *ref) { assert(ref); - if (ref->type & GIT_REF_OID) + if (ref->flags & GIT_REF_OID) return GIT_REF_OID; - if (ref->type & GIT_REF_SYMBOLIC) + if (ref->flags & GIT_REF_SYMBOLIC) return GIT_REF_SYMBOLIC; return GIT_REF_INVALID; } +int git_reference_is_packed(git_reference *ref) +{ + assert(ref); + return !!(ref->flags & GIT_REF_PACKED); +} + const char *git_reference_name(git_reference *ref) { assert(ref); @@ -1276,110 +1188,129 @@ const git_oid *git_reference_oid(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_OID) == 0) + if ((ref->flags & GIT_REF_OID) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; - - return &((reference_oid *)ref)->oid; + return &ref->target.oid; } const char *git_reference_target(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_SYMBOLIC) == 0) + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; - - return ((reference_symbolic *)ref)->target; + return ref->target.symbolic; } -/** - * Setters - */ +int git_reference_create_symbolic( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) +{ + char normalized[GIT_REFNAME_MAX]; + git_reference *ref = NULL; + if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) + return -1; + + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; + + if (reference_alloc(&ref, repo, normalized) < 0) + return -1; + + ref->flags |= GIT_REF_SYMBOLIC; + + /* set the target; this will normalize the name automatically + * and write the reference on disk */ + if (git_reference_set_target(ref, target) < 0) { + git_reference_free(ref); + return -1; + } + if (ref_out == NULL) { + git_reference_free(ref); + } else { + *ref_out = ref; + } + + return 0; +} + +int git_reference_create_oid( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force) +{ + git_reference *ref = NULL; + char normalized[GIT_REFNAME_MAX]; + + if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) + return -1; + + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; + + if (reference_alloc(&ref, repo, name) < 0) + return -1; + + ref->flags |= GIT_REF_OID; + + /* set the oid; this will write the reference on disk */ + if (git_reference_set_oid(ref, id) < 0) { + git_reference_free(ref); + return -1; + } + + if (ref_out == NULL) { + git_reference_free(ref); + } else { + *ref_out = ref; + } + + return 0; +} /* * Change the OID target of a reference. * - * For loose references, just change the oid in memory - * and overwrite the file in disk. + * For both loose and packed references, just change + * the oid in memory and (over)write the file in disk. * - * For packed files, this is not pretty: - * For performance reasons, we write the new reference - * loose on disk (it replaces the old on the packfile), - * but we cannot invalidate the pointer to the reference, - * and most importantly, the `packfile` object must stay - * consistent with the representation of the packfile - * on disk. This is what we need to: - * - * 1. Copy the reference - * 2. Change the oid on the original - * 3. Write the original to disk - * 4. Write the original to the loose cache - * 5. Replace the original with the copy (old reference) in the packfile cache + * We do not repack packed references because of performance + * reasons. */ int git_reference_set_oid(git_reference *ref, const git_oid *id) { - reference_oid *ref_oid; - reference_oid *ref_old = NULL; - int error = GIT_SUCCESS; + git_odb *odb = NULL; - if ((ref->type & GIT_REF_OID) == 0) - return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference"); - - ref_oid = (reference_oid *)ref; + if ((ref->flags & GIT_REF_OID) == 0) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); + return -1; + } assert(ref->owner); + if (git_repository_odb__weakptr(&odb, ref->owner) < 0) + return -1; + /* Don't let the user create references to OIDs that * don't exist in the ODB */ - if (!git_odb_exists(git_repository_database(ref->owner), id)) - return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB"); - - /* duplicate the reference; - * this copy will stay on the packfile cache */ - if (ref->type & GIT_REF_PACKED) { - ref_old = git__malloc(sizeof(reference_oid)); - if (ref_old == NULL) - return GIT_ENOMEM; - - ref_old->ref.name = git__strdup(ref->name); - if (ref_old->ref.name == NULL) { - free(ref_old); - return GIT_ENOMEM; - } + if (!git_odb_exists(odb, id)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); + return -1; } - git_oid_cpy(&ref_oid->oid, id); - ref->type &= ~GIT_REF_HAS_PEEL; + /* Update the OID value on `ref` */ + git_oid_cpy(&ref->target.oid, id); - error = loose_write(ref); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref->type & GIT_REF_PACKED) { - /* insert the original on the loose cache */ - error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref); - if (error < GIT_SUCCESS) - goto cleanup; - - ref->type &= ~GIT_REF_PACKED; - - /* replace the original in the packfile with the copy */ - error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old); - if (error < GIT_SUCCESS) - goto cleanup; - } - - return GIT_SUCCESS; - -cleanup: - reference_free((git_reference *)ref_old); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference"); + /* Write back to disk */ + return loose_write(ref); } /* @@ -1391,149 +1322,177 @@ cleanup: */ int git_reference_set_target(git_reference *ref, const char *target) { - reference_symbolic *ref_sym; + char normalized[GIT_REFNAME_MAX]; - if ((ref->type & GIT_REF_SYMBOLIC) == 0) - return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference"); + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { + giterr_set(GITERR_REFERENCE, + "Cannot set symbolic target on a direct reference"); + return -1; + } - ref_sym = (reference_symbolic *)ref; + if (normalize_name(normalized, sizeof(normalized), target, 0)) + return -1; - free(ref_sym->target); - ref_sym->target = git__strdup(target); - if (ref_sym->target == NULL) - return GIT_ENOMEM; + git__free(ref->target.symbolic); + ref->target.symbolic = git__strdup(normalized); + GITERR_CHECK_ALLOC(ref->target.symbolic); return loose_write(ref); } -/** - * Other - */ - -/* - * Delete a reference. - * - * If the reference is packed, this is an expensive - * operation. We need to remove the reference from - * the memory cache and then rewrite the whole pack - * - * If the reference is loose, we remove it on - * the filesystem and update the in-memory cache - * accordingly. We also make sure that an older version - * of it doesn't exist as a packed reference. If this - * is the case, this packed reference is removed as well. - * - * This obviously invalidates the `ref` pointer. - */ -int git_reference_delete(git_reference *ref) +int git_reference_rename(git_reference *ref, const char *new_name, int force) { - int error; - git_reference *reference; + int result; + git_buf aux_path = GIT_BUF_INIT; + char normalized[GIT_REFNAME_MAX]; - assert(ref); + const char *head_target = NULL; + git_reference *head = NULL; - if (ref->type & GIT_REF_PACKED) { - /* load the existing packfile */ - if ((error = packed_load(ref->owner)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete reference"); - - git_hashtable_remove(ref->owner->references.packfile, ref->name); - error = packed_write(ref->owner); + if (normalize_name(normalized, sizeof(normalized), + new_name, ref->flags & GIT_REF_OID) < 0) + return -1; + + if (reference_can_write(ref->owner, normalized, ref->name, force) < 0) + return -1; + + /* Initialize path now so we won't get an allocation failure once + * we actually start removing things. */ + if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) + return -1; + + /* + * Now delete the old ref and remove an possibly existing directory + * named `new_name`. Note that using the internal `reference_delete` + * method deletes the ref from disk but doesn't free the pointer, so + * we can still access the ref's attributes for creating the new one + */ + if (reference_delete(ref) < 0) + goto cleanup; + + /* + * Finally we can create the new reference. + */ + if (ref->flags & GIT_REF_SYMBOLIC) { + result = git_reference_create_symbolic( + NULL, ref->owner, new_name, ref->target.symbolic, force); } else { - char full_path[GIT_PATH_MAX]; - git__joinpath(full_path, ref->owner->path_repository, ref->name); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - error = gitfo_unlink(full_path); - if (error < GIT_SUCCESS) - goto cleanup; + result = git_reference_create_oid( + NULL, ref->owner, new_name, &ref->target.oid, force); + } - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist - */ - if (!git_reference_lookup(&reference, ref->owner, ref->name)) { - assert((reference->type & GIT_REF_PACKED) != 0); - error = git_reference_delete(reference); + if (result < 0) + goto rollback; + + /* + * Check if we have to update HEAD. + */ + if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_REFERENCE, + "Failed to update HEAD after renaming reference"); + goto cleanup; + } + + head_target = git_reference_target(head); + + if (head_target && !strcmp(head_target, ref->name)) { + if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { + giterr_set(GITERR_REFERENCE, + "Failed to update HEAD after renaming reference"); + goto cleanup; } } -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference"); -} + /* + * Rename the reflog file. + */ + if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + goto cleanup; -int git_reference_rename(git_reference *ref, const char *new_name) -{ - return reference_rename(ref, new_name, 0); -} - -int git_reference_rename_f(git_reference *ref, const char *new_name) -{ - return reference_rename(ref, new_name, 1); -} - -int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) -{ - git_repository *repo; - int error, i; - - assert(resolved_ref && ref); - *resolved_ref = NULL; - - if ((error = loose_update(ref)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to resolve reference"); - - repo = ref->owner; - - for (i = 0; i < MAX_NESTING_LEVEL; ++i) { - reference_symbolic *ref_sym; - - *resolved_ref = ref; - - if (ref->type & GIT_REF_OID) - return GIT_SUCCESS; - - ref_sym = (reference_symbolic *)ref; - if ((error = git_reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS) - return error; + if (git_path_exists(aux_path.ptr) == true) { + if (git_reflog_rename(ref, new_name) < 0) + goto cleanup; + } else { + giterr_clear(); } - return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested"); + /* + * Change the name of the reference given by the user. + */ + git__free(ref->name); + ref->name = git__strdup(new_name); + + /* The reference is no longer packed */ + ref->flags &= ~GIT_REF_PACKED; + + git_reference_free(head); + git_buf_free(&aux_path); + return 0; + +cleanup: + git_reference_free(head); + git_buf_free(&aux_path); + return -1; + +rollback: + /* + * Try to create the old reference again, ignore failures + */ + if (ref->flags & GIT_REF_SYMBOLIC) + git_reference_create_symbolic( + NULL, ref->owner, ref->name, ref->target.symbolic, 0); + else + git_reference_create_oid( + NULL, ref->owner, ref->name, &ref->target.oid, 0); + + /* The reference is no longer packed */ + ref->flags &= ~GIT_REF_PACKED; + + git_buf_free(&aux_path); + return -1; +} + +int git_reference_resolve(git_reference **ref_out, git_reference *ref) +{ + if (ref->flags & GIT_REF_OID) + return git_reference_lookup(ref_out, ref->owner, ref->name); + else + return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1); } int git_reference_packall(git_repository *repo) { - int error; + if (packed_load(repo) < 0 || /* load the existing packfile */ + packed_loadloose(repo) < 0 || /* add all the loose refs */ + packed_write(repo) < 0) /* write back to disk */ + return -1; - /* load the existing packfile */ - if ((error = packed_load(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to pack references"); - - /* update it in-memory with all the loose references */ - if ((error = packed_loadloose(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to pack references"); - - /* write it back to disk */ - return packed_write(repo); + return 0; } -int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload) +int git_reference_foreach( + git_repository *repo, + unsigned int list_flags, + int (*callback)(const char *, void *), + void *payload) { - int error; + int result; struct dirent_list_data data; - char refs_path[GIT_PATH_MAX]; + git_buf refs_path = GIT_BUF_INIT; /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; - void *GIT_UNUSED(_unused); + void *ref; + GIT_UNUSED(ref); - if ((error = packed_load(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to list references"); + if (packed_load(repo) < 0) + return -1; - GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused, - if ((error = callback(ref_name, payload)) < GIT_SUCCESS) - return git__throw(error, "Failed to list references. User callback failed"); - ); + git_strmap_foreach(repo->references.packfile, ref_name, ref, { + if (callback(ref_name, payload) < 0) + return 0; + }); } /* now list the loose references, trying not to @@ -1545,19 +1504,25 @@ int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*ca data.callback = callback; data.callback_payload = payload; + if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) + return -1; - git__joinpath(refs_path, repo->path_repository, GIT_REFS_DIR); - return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data); + result = git_path_direach(&refs_path, _dirent_loose_listall, &data); + git_buf_free(&refs_path); + + return result; } -int cb__reflist_add(const char *ref, void *data) +static int cb__reflist_add(const char *ref, void *data) { return git_vector_insert((git_vector *)data, git__strdup(ref)); } -int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags) +int git_reference_list( + git_strarray *array, + git_repository *repo, + unsigned int list_flags) { - int error; git_vector ref_list; assert(array && repo); @@ -1565,73 +1530,44 @@ int git_reference_listall(git_strarray *array, git_repository *repo, unsigned in array->strings = NULL; array->count = 0; - if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_init(&ref_list, 8, NULL) < 0) + return -1; - error = git_reference_listcb(repo, list_flags, &cb__reflist_add, (void *)&ref_list); - - if (error < GIT_SUCCESS) { + if (git_reference_foreach( + repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) { git_vector_free(&ref_list); - return error; + return -1; } array->strings = (char **)ref_list.contents; array->count = ref_list.length; - return GIT_SUCCESS; + return 0; } - - - -/***************************************** - * Init/free (repository API) - *****************************************/ -int git_repository__refcache_init(git_refcache *refs) +int git_reference_reload(git_reference *ref) { - assert(refs); - - refs->loose_cache = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)strcmp); - - /* packfile loaded lazily */ - refs->packfile = NULL; - - return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM; + return reference_lookup(ref); } void git_repository__refcache_free(git_refcache *refs) { - git_reference *reference; - const void *GIT_UNUSED(_unused); - assert(refs); - GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference, - reference_free(reference); - ); - - git_hashtable_free(refs->loose_cache); - if (refs->packfile) { - GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference, - reference_free(reference); - ); + struct packref *reference; - git_hashtable_free(refs->packfile); + git_strmap_foreach_value(refs->packfile, reference, { + git__free(reference); + }); + + git_strmap_free(refs->packfile); } } - - -/***************************************** - * Name normalization - *****************************************/ -static int check_valid_ref_char(char ch) +static int is_valid_ref_char(char ch) { - if (ch <= ' ') - return GIT_ERROR; + if ((unsigned) ch <= ' ') + return 0; switch (ch) { case '~': @@ -1641,48 +1577,53 @@ static int check_valid_ref_char(char ch) case '?': case '[': case '*': - return GIT_ERROR; - break; - + return 0; default: - return GIT_SUCCESS; + return 1; } } -static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) +static int normalize_name( + char *buffer_out, + size_t out_size, + const char *name, + int is_oid_ref) { const char *name_end, *buffer_out_start; - char *current; + const char *current; int contains_a_slash = 0; assert(name && buffer_out); buffer_out_start = buffer_out; - current = (char *)name; + current = name; name_end = name + strlen(name); + /* Terminating null byte */ + out_size--; + /* A refname can not be empty */ if (name_end == name) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty"); + goto invalid_name; /* A refname can not end with a dot or a slash */ if (*(name_end - 1) == '.' || *(name_end - 1) == '/') - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash"); + goto invalid_name; - while (current < name_end) { - if (check_valid_ref_char(*current)) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters"); + while (current < name_end && out_size) { + if (!is_valid_ref_char(*current)) + goto invalid_name; if (buffer_out > buffer_out_start) { char prev = *(buffer_out - 1); /* A refname can not start with a dot nor contain a double dot */ if (*current == '.' && ((prev == '.') || (prev == '/'))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot"); + goto invalid_name; /* '@{' is forbidden within a refname */ if (*current == '{' && prev == '@') - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'"); + goto invalid_name; /* Prevent multiple slashes from being added to the output */ if (*current == '/' && prev == '/') { @@ -1695,17 +1636,25 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) contains_a_slash = 1; *buffer_out++ = *current++; + out_size--; } + if (!out_size) + goto invalid_name; + /* Object id refname have to contain at least one slash, except * for HEAD in a detached state or MERGE_HEAD if we're in the * middle of a merge */ - if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes"); + if (is_oid_ref && + !contains_a_slash && + strcmp(name, GIT_HEAD_FILE) != 0 && + strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && + strcmp(name, GIT_FETCH_HEAD_FILE) != 0) + goto invalid_name; /* A refname can not end with ".lock" */ if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'"); + goto invalid_name; *buffer_out = '\0'; @@ -1715,19 +1664,44 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) */ if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || strcmp(buffer_out_start, GIT_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'"); + goto invalid_name; - return GIT_SUCCESS; + return 0; + +invalid_name: + giterr_set(GITERR_REFERENCE, "The given reference name is not valid"); + return -1; } -int git_reference__normalize_name(char *buffer_out, const char *name) +int git_reference__normalize_name( + char *buffer_out, + size_t out_size, + const char *name) { - return normalize_name(buffer_out, name, 0); + return normalize_name(buffer_out, out_size, name, 0); } -int git_reference__normalize_name_oid(char *buffer_out, const char *name) +int git_reference__normalize_name_oid( + char *buffer_out, + size_t out_size, + const char *name) { - return normalize_name(buffer_out, name, 1); + return normalize_name(buffer_out, out_size, name, 1); } +#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) + +int git_reference_cmp(git_reference *ref1, git_reference *ref2) +{ + assert(ref1 && ref2); + + /* let's put symbolic refs before OIDs */ + if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK)) + return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1; + + if (ref1->flags & GIT_REF_SYMBOLIC) + return strcmp(ref1->target.symbolic, ref2->target.symbolic); + + return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); +} diff --git a/src/refs.h b/src/refs.h index b8f3e2f6d..369e91e1c 100644 --- a/src/refs.h +++ b/src/refs.h @@ -1,43 +1,81 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_refs_h__ #define INCLUDE_refs_h__ #include "common.h" #include "git2/oid.h" #include "git2/refs.h" -#include "hashtable.h" +#include "strmap.h" #define GIT_REFS_DIR "refs/" #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" #define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" #define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" +#define GIT_REFS_DIR_MODE 0777 +#define GIT_REFS_FILE_MODE 0666 + +#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" #define GIT_SYMREF "ref: " #define GIT_PACKEDREFS_FILE "packed-refs" #define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled " -#define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100 +#define GIT_PACKEDREFS_FILE_MODE 0666 #define GIT_HEAD_FILE "HEAD" +#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" #define GIT_MERGE_HEAD_FILE "MERGE_HEAD" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" +#define GIT_REFNAME_MAX 1024 + struct git_reference { + unsigned int flags; git_repository *owner; char *name; - unsigned int type; time_t mtime; + + union { + git_oid oid; + char *symbolic; + } target; }; typedef struct { - git_hashtable *packfile; - git_hashtable *loose_cache; + git_strmap *packfile; time_t packfile_time; } git_refcache; - void git_repository__refcache_free(git_refcache *refs); -int git_repository__refcache_init(git_refcache *refs); -int git_reference__normalize_name(char *buffer_out, const char *name); -int git_reference__normalize_name_oid(char *buffer_out, const char *name); +int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name); +int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name); + +/** + * Lookup a reference by name and try to resolve to an OID. + * + * You can control how many dereferences this will attempt to resolve the + * reference with the `max_deref` parameter, or pass -1 to use a sane + * default. If you pass 0 for `max_deref`, this will not attempt to resolve + * the reference. For any value of `max_deref` other than 0, not + * successfully resolving the reference will be reported as an error. + + * The generated reference must be freed by the user. + * + * @param reference_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, ref/heads/master, refs/tags/v0.1.0, ...) + * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value + * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref + */ +int git_reference_lookup_resolved( + git_reference **reference_out, + git_repository *repo, + const char *name, + int max_deref); #endif diff --git a/src/refspec.c b/src/refspec.c new file mode 100644 index 000000000..b6b1158b7 --- /dev/null +++ b/src/refspec.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/errors.h" + +#include "common.h" +#include "refspec.h" +#include "util.h" +#include "posix.h" + +int git_refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) { + refspec->src = git__strdup(str); + GITERR_CHECK_ALLOC(refspec->src); + return 0; + } + + refspec->src = git__strndup(str, delim - str); + GITERR_CHECK_ALLOC(refspec->src); + + refspec->dst = git__strdup(delim + 1); + if (refspec->dst == NULL) { + git__free(refspec->src); + refspec->src = NULL; + return -1; + } + + return 0; +} + +const char *git_refspec_src(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->src; +} + +const char *git_refspec_dst(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->dst; +} + +int git_refspec_force(const git_refspec *refspec) +{ + assert(refspec); + + return refspec->force; +} + +int git_refspec_src_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->src == NULL) + return false; + + return (p_fnmatch(refspec->src, refname, 0) == 0); +} + +int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name) +{ + size_t baselen, namelen; + + baselen = strlen(spec->dst); + if (outlen <= baselen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_EBUFS; + } + + /* + * No '*' at the end means that it's mapped to one specific local + * branch, so no actual transformation is needed. + */ + if (spec->dst[baselen - 1] != '*') { + memcpy(out, spec->dst, baselen + 1); /* include '\0' */ + return 0; + } + + /* There's a '*' at the end, so remove its length */ + baselen--; + + /* skip the prefix, -1 is for the '*' */ + name += strlen(spec->src) - 1; + + namelen = strlen(name); + + if (outlen <= baselen + namelen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_EBUFS; + } + + memcpy(out, spec->dst, baselen); + memcpy(out + baselen, name, namelen + 1); + + return 0; +} + +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) +{ + if (git_buf_sets(out, spec->dst) < 0) + return -1; + + /* + * No '*' at the end means that it's mapped to one specific local + * branch, so no actual transformation is needed. + */ + if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') + return 0; + + git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ + git_buf_puts(out, name + strlen(spec->src) - 1); + + if (git_buf_oom(out)) + return -1; + + return 0; +} + diff --git a/src/refspec.h b/src/refspec.h new file mode 100644 index 000000000..2db504910 --- /dev/null +++ b/src/refspec.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_refspec_h__ +#define INCLUDE_refspec_h__ + +#include "git2/refspec.h" +#include "buffer.h" + +struct git_refspec { + struct git_refspec *next; + char *src; + char *dst; + unsigned int force :1, + pattern :1, + matching :1; +}; + +int git_refspec_parse(struct git_refspec *refspec, const char *str); + +/** + * Transform a reference to its target following the refspec's rules, + * and writes the results into a git_buf. + * + * @param out where to store the target name + * @param spec the refspec + * @param name the name of the reference to transform + * @return 0 or error if buffer allocation fails + */ +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name); + +#endif diff --git a/src/remote.c b/src/remote.c new file mode 100644 index 000000000..8d6076107 --- /dev/null +++ b/src/remote.c @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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/remote.h" +#include "git2/config.h" +#include "git2/types.h" + +#include "config.h" +#include "repository.h" +#include "remote.h" +#include "fetch.h" +#include "refs.h" + +#include + +static int refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) { + giterr_set(GITERR_NET, "Invalid refspec, missing ':'"); + return -1; + } + + refspec->src = git__strndup(str, delim - str); + GITERR_CHECK_ALLOC(refspec->src); + + refspec->dst = git__strdup(delim + 1); + GITERR_CHECK_ALLOC(refspec->dst); + + return 0; +} + +static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var) +{ + int error; + const char *val; + + if ((error = git_config_get_string(&val, cfg, var)) < 0) + return error; + + return refspec_parse(refspec, val); +} + +int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) +{ + git_remote *remote; + + /* name is optional */ + assert(out && repo && url); + + remote = git__malloc(sizeof(git_remote)); + GITERR_CHECK_ALLOC(remote); + + memset(remote, 0x0, sizeof(git_remote)); + remote->repo = repo; + remote->check_cert = 1; + + if (git_vector_init(&remote->refs, 32, NULL) < 0) + return -1; + + remote->url = git__strdup(url); + GITERR_CHECK_ALLOC(remote->url); + + if (name != NULL) { + remote->name = git__strdup(name); + GITERR_CHECK_ALLOC(remote->name); + } + + if (fetch != NULL) { + if (refspec_parse(&remote->fetch, fetch) < 0) + goto on_error; + } + + *out = remote; + return 0; + +on_error: + git_remote_free(remote); + return -1; +} + +int git_remote_load(git_remote **out, git_repository *repo, const char *name) +{ + git_remote *remote; + git_buf buf = GIT_BUF_INIT; + const char *val; + int error = 0; + git_config *config; + + assert(out && repo && name); + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + remote = git__malloc(sizeof(git_remote)); + GITERR_CHECK_ALLOC(remote); + + memset(remote, 0x0, sizeof(git_remote)); + remote->check_cert = 1; + remote->name = git__strdup(name); + GITERR_CHECK_ALLOC(remote->name); + + if (git_vector_init(&remote->refs, 32, NULL) < 0) { + error = -1; + goto cleanup; + } + + if (git_buf_printf(&buf, "remote.%s.url", name) < 0) { + error = -1; + goto cleanup; + } + + if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) + goto cleanup; + + remote->repo = repo; + remote->url = git__strdup(val); + GITERR_CHECK_ALLOC(remote->url); + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { + error = -1; + goto cleanup; + } + + error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.push", name) < 0) { + error = -1; + goto cleanup; + } + + error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + *out = remote; + +cleanup: + git_buf_free(&buf); + + if (error < 0) + git_remote_free(remote); + + return error; +} + +int git_remote_save(const git_remote *remote) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT; + + if (git_repository_config__weakptr(&config, remote->repo) < 0) + return -1; + + if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + return -1; + + if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { + git_buf_free(&buf); + return -1; + } + + if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { + git_buf_clear(&buf); + git_buf_clear(&value); + git_buf_printf(&buf, "remote.%s.fetch", remote->name); + if (remote->fetch.force) + git_buf_putc(&value, '+'); + git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst); + if (git_buf_oom(&buf) || git_buf_oom(&value)) + return -1; + + if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + goto on_error; + } + + if (remote->push.src != NULL && remote->push.dst != NULL) { + git_buf_clear(&buf); + git_buf_clear(&value); + git_buf_printf(&buf, "remote.%s.push", remote->name); + if (remote->push.force) + git_buf_putc(&value, '+'); + git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst); + if (git_buf_oom(&buf) || git_buf_oom(&value)) + return -1; + + if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + goto on_error; + } + + git_buf_free(&buf); + git_buf_free(&value); + + return 0; + +on_error: + git_buf_free(&buf); + git_buf_free(&value); + return -1; +} + +const char *git_remote_name(git_remote *remote) +{ + assert(remote); + return remote->name; +} + +const char *git_remote_url(git_remote *remote) +{ + assert(remote); + return remote->url; +} + +int git_remote_set_fetchspec(git_remote *remote, const char *spec) +{ + git_refspec refspec; + + assert(remote && spec); + + if (refspec_parse(&refspec, spec) < 0) + return -1; + + git__free(remote->fetch.src); + git__free(remote->fetch.dst); + remote->fetch.src = refspec.src; + remote->fetch.dst = refspec.dst; + + return 0; +} + +const git_refspec *git_remote_fetchspec(git_remote *remote) +{ + assert(remote); + return &remote->fetch; +} + +int git_remote_set_pushspec(git_remote *remote, const char *spec) +{ + git_refspec refspec; + + assert(remote && spec); + + if (refspec_parse(&refspec, spec) < 0) + return -1; + + git__free(remote->push.src); + git__free(remote->push.dst); + remote->push.src = refspec.src; + remote->push.dst = refspec.dst; + + return 0; +} + +const git_refspec *git_remote_pushspec(git_remote *remote) +{ + assert(remote); + return &remote->push; +} + +int git_remote_connect(git_remote *remote, int direction) +{ + git_transport *t; + + assert(remote); + + if (git_transport_new(&t, remote->url) < 0) + return -1; + + t->check_cert = remote->check_cert; + if (t->connect(t, direction) < 0) { + goto on_error; + } + + remote->transport = t; + + return 0; + +on_error: + t->free(t); + return -1; +} + +int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) +{ + assert(remote); + + if (!remote->transport || !remote->transport->connected) { + giterr_set(GITERR_NET, "The remote is not connected"); + return -1; + } + + return remote->transport->ls(remote->transport, list_cb, payload); +} + +int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +{ + int error; + + assert(remote && bytes && stats); + + if ((error = git_fetch_negotiate(remote)) < 0) + return error; + + return git_fetch_download_pack(remote, bytes, stats); +} + +int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) +{ + int error = 0; + unsigned int i = 0; + git_buf refname = GIT_BUF_INIT; + git_oid old; + git_vector *refs = &remote->refs; + git_remote_head *head; + git_reference *ref; + struct git_refspec *spec = &remote->fetch; + + assert(remote); + + if (refs->length == 0) + return 0; + + /* HEAD is only allowed to be the first in the list */ + head = refs->contents[0]; + if (!strcmp(head->name, GIT_HEAD_FILE)) { + if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) + return -1; + + i = 1; + git_reference_free(ref); + } + + for (; i < refs->length; ++i) { + head = refs->contents[i]; + + if (git_refspec_transform_r(&refname, spec, head->name) < 0) + goto on_error; + + error = git_reference_name_to_oid(&old, remote->repo, refname.ptr); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + if (error == GIT_ENOTFOUND) + memset(&old, 0, GIT_OID_RAWSZ); + + if (!git_oid_cmp(&old, &head->oid)) + continue; + + if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) + break; + + git_reference_free(ref); + + if (cb != NULL) { + if (cb(refname.ptr, &old, &head->oid) < 0) + goto on_error; + } + } + + git_buf_free(&refname); + return 0; + +on_error: + git_buf_free(&refname); + return -1; + +} + +int git_remote_connected(git_remote *remote) +{ + assert(remote); + return remote->transport == NULL ? 0 : remote->transport->connected; +} + +void git_remote_disconnect(git_remote *remote) +{ + assert(remote); + + if (remote->transport != NULL && remote->transport->connected) + remote->transport->close(remote->transport); +} + +void git_remote_free(git_remote *remote) +{ + if (remote == NULL) + return; + + if (remote->transport != NULL) { + git_remote_disconnect(remote); + + remote->transport->free(remote->transport); + remote->transport = NULL; + } + + git_vector_free(&remote->refs); + + git__free(remote->fetch.src); + git__free(remote->fetch.dst); + git__free(remote->push.src); + git__free(remote->push.dst); + git__free(remote->url); + git__free(remote->name); + git__free(remote); +} + +struct cb_data { + git_vector *list; + regex_t *preg; +}; + +static int remote_list_cb(const char *name, const char *value, void *data_) +{ + struct cb_data *data = (struct cb_data *)data_; + size_t nmatch = 2; + regmatch_t pmatch[2]; + GIT_UNUSED(value); + + 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); + + if (git_vector_insert(data->list, remote_name) < 0) + return -1; + } + + return 0; +} + +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) + return -1; + + if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) { + giterr_set(GITERR_OS, "Remote catch regex failed to compile"); + return -1; + } + + 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); + } + + git_vector_free(&list); + return error; + } + + remotes_list->strings = (char **)list.contents; + remotes_list->count = list.length; + + return 0; +} + +int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url) +{ + git_buf buf = GIT_BUF_INIT; + + if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) + return -1; + + if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0) + goto on_error; + + git_buf_free(&buf); + + if (git_remote_save(*out) < 0) + goto on_error; + + return 0; + +on_error: + git_buf_free(&buf); + git_remote_free(*out); + return -1; +} + +void git_remote_check_cert(git_remote *remote, int check) +{ + assert(remote); + + remote->check_cert = check; +} diff --git a/src/remote.h b/src/remote.h new file mode 100644 index 000000000..0949ad434 --- /dev/null +++ b/src/remote.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_remote_h__ +#define INCLUDE_remote_h__ + +#include "refspec.h" +#include "transport.h" +#include "repository.h" + +struct git_remote { + char *name; + char *url; + git_vector refs; + struct git_refspec fetch; + struct git_refspec push; + git_transport *transport; + git_repository *repo; + unsigned int need_pack:1, + check_cert; +}; + +#endif diff --git a/src/repository.c b/src/repository.c index 32ca8dd79..718170839 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1,28 +1,11 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include +#include #include "git2/object.h" @@ -32,271 +15,45 @@ #include "tag.h" #include "blob.h" #include "fileops.h" - +#include "config.h" #include "refs.h" #define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" +#define GIT_FILE_CONTENT_PREFIX "gitdir:" + #define GIT_BRANCH_MASTER "master" -typedef struct { - char *path_repository; - unsigned is_bare:1, has_been_reinit:1; -} repo_init; +#define GIT_REPO_VERSION 0 -/* - * Git repository open methods - * - * Open a repository object from its path - */ -static int assign_repository_dirs( - git_repository *repo, - const char *git_dir, - const char *git_object_directory, - const char *git_index_file, - const char *git_work_tree) +static void drop_odb(git_repository *repo) { - char path_aux[GIT_PATH_MAX]; - int error = GIT_SUCCESS; + if (repo->_odb != NULL) { + GIT_REFCOUNT_OWN(repo->_odb, NULL); + git_odb_free(repo->_odb); + repo->_odb = NULL; + } +} - assert(repo); - - if (git_dir == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to open repository. Git dir not found"); - - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_dir); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); - - /* store GIT_DIR */ - repo->path_repository = git__strdup(path_aux); - if (repo->path_repository == NULL) - return GIT_ENOMEM; - - /* path to GIT_OBJECT_DIRECTORY */ - if (git_object_directory == NULL) - git__joinpath(path_aux, repo->path_repository, GIT_OBJECTS_DIR); - else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_object_directory); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); +static void drop_config(git_repository *repo) +{ + if (repo->_config != NULL) { + GIT_REFCOUNT_OWN(repo->_config, NULL); + git_config_free(repo->_config); + repo->_config = NULL; } - /* Store GIT_OBJECT_DIRECTORY */ - repo->path_odb = git__strdup(path_aux); - if (repo->path_odb == NULL) - return GIT_ENOMEM; + git_repository__cvar_cache_clear(repo); +} - /* path to GIT_WORK_TREE */ - if (git_work_tree == NULL) - repo->is_bare = 1; - else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_work_tree); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); - - /* Store GIT_WORK_TREE */ - repo->path_workdir = git__strdup(path_aux); - if (repo->path_workdir == NULL) - return GIT_ENOMEM; - - /* Path to GIT_INDEX_FILE */ - if (git_index_file == NULL) - git__joinpath(path_aux, repo->path_repository, GIT_INDEX_FILE); - else { - error = gitfo_prettify_file_path(path_aux, sizeof(path_aux), git_index_file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); - } - - /* store GIT_INDEX_FILE */ - repo->path_index = git__strdup(path_aux); - if (repo->path_index == NULL) - return GIT_ENOMEM; +static void drop_index(git_repository *repo) +{ + if (repo->_index != NULL) { + GIT_REFCOUNT_OWN(repo->_index, NULL); + git_index_free(repo->_index); + repo->_index = NULL; } - - return GIT_SUCCESS; -} - -static int check_repository_dirs(git_repository *repo) -{ - char path_aux[GIT_PATH_MAX]; - - if (gitfo_isdir(repo->path_repository) < GIT_SUCCESS) - return git__throw(GIT_ENOTAREPO, "`%s` is not a folder", repo->path_repository); - - /* Ensure GIT_OBJECT_DIRECTORY exists */ - if (gitfo_isdir(repo->path_odb) < GIT_SUCCESS) - return git__throw(GIT_ENOTAREPO, "`%s` does not exist", repo->path_odb); - - /* Ensure HEAD file exists */ - git__joinpath(path_aux, repo->path_repository, GIT_HEAD_FILE); - if (gitfo_exists(path_aux) < 0) - return git__throw(GIT_ENOTAREPO, "HEAD file is missing"); - - return GIT_SUCCESS; -} - -static int guess_repository_dirs(git_repository *repo, const char *repository_path) -{ - char buffer[GIT_PATH_MAX]; - const char *path_work_tree = NULL; - - /* Git directory name */ - if (git__basename_r(buffer, sizeof(buffer), repository_path) < 0) - return git__throw(GIT_EINVALIDPATH, "Unable to parse folder name from `%s`", repository_path); - - if (strcmp(buffer, DOT_GIT) == 0) { - /* Path to working dir */ - if (git__dirname_r(buffer, sizeof(buffer), repository_path) < 0) - return git__throw(GIT_EINVALIDPATH, "Unable to parse parent folder name from `%s`", repository_path); - path_work_tree = buffer; - } - - return assign_repository_dirs(repo, repository_path, NULL, NULL, path_work_tree); -} - -static git_repository *repository_alloc() -{ - int error; - - git_repository *repo = git__malloc(sizeof(git_repository)); - if (!repo) - return NULL; - - memset(repo, 0x0, sizeof(git_repository)); - - error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); - if (error < GIT_SUCCESS) { - free(repo); - return NULL; - } - - if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { - free(repo); - return NULL; - } - - return repo; -} - -static int init_odb(git_repository *repo) -{ - return git_odb_open(&repo->db, repo->path_odb); /* TODO: Move odb.c to new error handling */ -} - -int git_repository_open3(git_repository **repo_out, - const char *git_dir, - git_odb *object_database, - const char *git_index_file, - const char *git_work_tree) -{ - git_repository *repo; - int error = GIT_SUCCESS; - - assert(repo_out); - - if (object_database == NULL) - return git__throw(GIT_EINVALIDARGS, "Failed to open repository. `object_database` can't be null"); - - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; - - error = assign_repository_dirs(repo, - git_dir, - NULL, - git_index_file, - git_work_tree); - - if (error < GIT_SUCCESS) - goto cleanup; - - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - repo->db = object_database; - - *repo_out = repo; - return GIT_SUCCESS; - -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); -} - - -int git_repository_open2(git_repository **repo_out, - const char *git_dir, - const char *git_object_directory, - const char *git_index_file, - const char *git_work_tree) -{ - git_repository *repo; - int error = GIT_SUCCESS; - - assert(repo_out); - - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; - - error = assign_repository_dirs(repo, - git_dir, - git_object_directory, - git_index_file, - git_work_tree); - - if (error < GIT_SUCCESS) - goto cleanup; - - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - *repo_out = repo; - return GIT_SUCCESS; - -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); -} - -int git_repository_open(git_repository **repo_out, const char *path) -{ - git_repository *repo; - int error = GIT_SUCCESS; - - assert(repo_out && path); - - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; - - error = guess_repository_dirs(repo, path); - if (error < GIT_SUCCESS) - goto cleanup; - - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - *repo_out = repo; - return GIT_SUCCESS; - -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); } void git_repository_free(git_repository *repo) @@ -306,173 +63,882 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); + git_attr_cache_flush(repo); + git_submodule_config_free(repo); - free(repo->path_workdir); - free(repo->path_index); - free(repo->path_repository); - free(repo->path_odb); + git__free(repo->path_repository); + git__free(repo->workdir); - if (repo->db != NULL) - git_odb_close(repo->db); + drop_config(repo); + drop_index(repo); + drop_odb(repo); - free(repo); + git__free(repo); } -git_odb *git_repository_database(git_repository *repo) +/* + * Git repository open methods + * + * Open a repository object from its path + */ +static bool valid_repository_path(git_buf *repository_path) { - assert(repo); - return repo->db; + /* Check OBJECTS_DIR first, since it will generate the longest path name */ + if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) + return false; + + /* Ensure HEAD file exists */ + if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return false; + + if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + return false; + + return true; } -static int repo_init_reinit(repo_init *results) +static git_repository *repository_alloc(void) { - /* TODO: reinit the repository */ - results->has_been_reinit = 1; - return git__throw(GIT_ENOTIMPLEMENTED, "Failed to reinitialize the repository. This feature is not yet implemented"); -} + git_repository *repo = git__malloc(sizeof(git_repository)); + if (!repo) + return NULL; -static int repo_init_createhead(git_repository *repo) -{ - git_reference *head_reference; - return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); /* TODO: finalize moving refs.c to new error handling */ -} + memset(repo, 0x0, sizeof(git_repository)); -static int repo_init_check_head_existence(char * repository_path) -{ - char temp_path[GIT_PATH_MAX]; - - git__joinpath(temp_path, repository_path, GIT_HEAD_FILE); - return gitfo_exists(temp_path); -} - -static int repo_init_structure(repo_init *results) -{ - const int mode = 0755; /* or 0777 ? */ - int error; - - char temp_path[GIT_PATH_MAX]; - char *git_dir = results->path_repository; - - if (gitfo_mkdir_recurs(git_dir, mode)) - return git__throw(GIT_ERROR, "Failed to initialize repository structure. Could not mkdir"); - - /* Creates the '/objects/info/' directory */ - git__joinpath(temp_path, git_dir, GIT_OBJECTS_INFO_DIR); - error = gitfo_mkdir_recurs(temp_path, mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); - - /* Creates the '/objects/pack/' directory */ - git__joinpath(temp_path, git_dir, GIT_OBJECTS_PACK_DIR); - error = gitfo_mkdir(temp_path, mode); - if (error < GIT_SUCCESS) - return git__throw(error, "Unable to create `%s` folder", temp_path); - - /* Creates the '/refs/heads/' directory */ - git__joinpath(temp_path, git_dir, GIT_REFS_HEADS_DIR); - error = gitfo_mkdir_recurs(temp_path, mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); - - /* Creates the '/refs/tags/' directory */ - git__joinpath(temp_path, git_dir, GIT_REFS_TAGS_DIR); - error = gitfo_mkdir(temp_path, mode); - if (error < GIT_SUCCESS) - return git__throw(error, "Unable to create `%s` folder", temp_path); - - /* TODO: what's left? templates? */ - - return GIT_SUCCESS; -} - -static int repo_init_find_dir(repo_init *results, const char* path) -{ - char temp_path[GIT_PATH_MAX]; - int error = GIT_SUCCESS; - - error = gitfo_prettify_dir_path(temp_path, sizeof(temp_path), path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find directory to initialize repository"); - - if (!results->is_bare) { - git__joinpath(temp_path, temp_path, GIT_DIR); + if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) { + git__free(repo); + return NULL; } - results->path_repository = git__strdup(temp_path); - if (results->path_repository == NULL) - return GIT_ENOMEM; + /* set all the entries in the cvar cache to `unset` */ + git_repository__cvar_cache_clear(repo); - return GIT_SUCCESS; + return repo; +} + +static int load_config_data(git_repository *repo) +{ + int is_bare; + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + /* Try to figure out if it's bare, default to non-bare if it's not set */ + if (git_config_get_bool(&is_bare, config, "core.bare") < 0) + repo->is_bare = 0; + else + repo->is_bare = is_bare; + + return 0; +} + +static int load_workdir(git_repository *repo, git_buf *parent_path) +{ + int error; + git_config *config; + const char *worktree; + git_buf worktree_buf = GIT_BUF_INIT; + + if (repo->is_bare) + return 0; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = git_config_get_string(&worktree, config, "core.worktree"); + if (!error && worktree != NULL) + repo->workdir = git__strdup(worktree); + else if (error != GIT_ENOTFOUND) + return error; + else { + giterr_clear(); + + if (parent_path && git_path_isdir(parent_path->ptr)) + repo->workdir = git_buf_detach(parent_path); + else { + git_path_dirname_r(&worktree_buf, repo->path_repository); + git_path_to_dir(&worktree_buf); + repo->workdir = git_buf_detach(&worktree_buf); + } + } + + GITERR_CHECK_ALLOC(repo->workdir); + + return 0; +} + +/* + * This function returns furthest offset into path where a ceiling dir + * is found, so we can stop processing the path at that point. + * + * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on + * the stack could remove directories name limits, but at the cost of doing + * repeated malloc/frees inside the loop below, so let's not do it now. + */ +static int find_ceiling_dir_offset( + const char *path, + const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + size_t len, max_len = 0, min_len; + + assert(path); + + min_len = (size_t)(git_path_root(path) + 1); + + if (ceiling_directories == NULL || min_len == 0) + return (int)min_len; + + for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { + for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); + len = sep - ceil; + + if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; + + if (p_realpath(buf, buf2) == NULL) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf2, len) && + path[len] == '/' && + len > max_len) + { + max_len = len; + } + } + + return (int)(max_len <= min_len ? min_len : max_len); +} + +/* + * Read the contents of `file_path` and set `path_out` to the repo dir that + * it points to. Before calling, set `path_out` to the base directory that + * should be used if the contents of `file_path` are a relative path. + */ +static int read_gitfile(git_buf *path_out, const char *file_path) +{ + int error = 0; + git_buf file = GIT_BUF_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); + + assert(path_out && file_path); + + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; + + git_buf_rtrim(&file); + + if (file.size <= prefix_len || + memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + { + giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path); + error = -1; + } + else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { + const char *gitlink = ((const char *)file.ptr) + prefix_len; + while (*gitlink && git__isspace(*gitlink)) gitlink++; + error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); + } + + git_buf_free(&file); + return error; +} + +static int find_repo( + git_buf *repo_path, + git_buf *parent_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + int error; + git_buf path = GIT_BUF_INIT; + struct stat st; + dev_t initial_device = 0; + bool try_with_dot_git = false; + int ceiling_offset; + + git_buf_free(repo_path); + + if ((error = git_path_prettify_dir(&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) + return error; + + while (!error && !git_buf_len(repo_path)) { + if (p_stat(path.ptr, &st) == 0) { + /* check that we have not crossed device boundaries */ + if (initial_device == 0) + initial_device = st.st_dev; + else if (st.st_dev != initial_device && + (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0) + break; + + if (S_ISDIR(st.st_mode)) { + if (valid_repository_path(&path)) { + git_path_to_dir(&path); + git_buf_set(repo_path, path.ptr, path.size); + break; + } + } + else if (S_ISREG(st.st_mode)) { + git_buf repo_link = GIT_BUF_INIT; + + if (!(error = read_gitfile(&repo_link, path.ptr))) { + if (valid_repository_path(&repo_link)) + git_buf_swap(repo_path, &repo_link); + + git_buf_free(&repo_link); + break; + } + git_buf_free(&repo_link); + } + } + + /* move up one directory level */ + if (git_path_dirname_r(&path, path.ptr) < 0) { + error = -1; + break; + } + + if (try_with_dot_git) { + /* if we tried original dir with and without .git AND either hit + * directory ceiling or NO_SEARCH was requested, then be done. + */ + if (path.ptr[ceiling_offset] == '\0' || + (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0) + break; + /* otherwise look first for .git item */ + error = git_buf_joinpath(&path, path.ptr, DOT_GIT); + } + try_with_dot_git = !try_with_dot_git; + } + + if (!error && parent_path != NULL) { + if (!git_buf_len(repo_path)) + git_buf_clear(parent_path); + else { + git_path_dirname_r(parent_path, path.ptr); + git_path_to_dir(parent_path); + } + if (git_buf_oom(parent_path)) + return -1; + } + + git_buf_free(&path); + + if (!git_buf_len(repo_path) && !error) { + giterr_set(GITERR_REPOSITORY, + "Could not find repository from '%s'", start_path); + error = GIT_ENOTFOUND; + } + + return error; +} + +int git_repository_open_ext( + git_repository **repo_ptr, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + int error; + git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; + git_repository *repo; + + *repo_ptr = NULL; + + if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) + return error; + + repo = repository_alloc(); + GITERR_CHECK_ALLOC(repo); + + repo->path_repository = git_buf_detach(&path); + GITERR_CHECK_ALLOC(repo->path_repository); + + if ((error = load_config_data(repo)) < 0 || + (error = load_workdir(repo, &parent)) < 0) + { + git_repository_free(repo); + return error; + } + + git_buf_free(&parent); + *repo_ptr = repo; + return 0; +} + +int git_repository_open(git_repository **repo_out, const char *path) +{ + return git_repository_open_ext( + repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); +} + +int git_repository_discover( + char *repository_path, + size_t size, + const char *start_path, + int across_fs, + const char *ceiling_dirs) +{ + git_buf path = GIT_BUF_INIT; + uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + int error; + + assert(start_path && repository_path && size > 0); + + *repository_path = '\0'; + + if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0) + return error != GIT_ENOTFOUND ? -1 : error; + + if (size < (size_t)(path.size + 1)) { + giterr_set(GITERR_REPOSITORY, + "The given buffer is too long to store the discovered path"); + git_buf_free(&path); + return -1; + } + + /* success: we discovered a repository */ + git_buf_copy_cstr(repository_path, size, &path); + git_buf_free(&path); + return 0; +} + +static int load_config( + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *system_config_path) +{ + git_buf config_path = GIT_BUF_INIT; + git_config *cfg = NULL; + + assert(repo && out); + + if (git_config_new(&cfg) < 0) + return -1; + + if (git_buf_joinpath( + &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) + goto on_error; + + if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) + goto on_error; + + git_buf_free(&config_path); + + if (global_config_path != NULL) { + if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) + goto on_error; + } + + if (system_config_path != NULL) { + if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) + goto on_error; + } + + *out = cfg; + return 0; + +on_error: + git_buf_free(&config_path); + git_config_free(cfg); + *out = NULL; + return -1; +} + +int git_repository_config__weakptr(git_config **out, git_repository *repo) +{ + if (repo->_config == NULL) { + git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; + int res; + + const char *global_config_path = NULL; + const char *system_config_path = NULL; + + if (git_config_find_global_r(&global_buf) == 0) + global_config_path = global_buf.ptr; + + if (git_config_find_system_r(&system_buf) == 0) + system_config_path = system_buf.ptr; + + res = load_config(&repo->_config, repo, global_config_path, system_config_path); + + git_buf_free(&global_buf); + git_buf_free(&system_buf); + + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_config, repo); + } + + *out = repo->_config; + return 0; +} + +int git_repository_config(git_config **out, git_repository *repo) +{ + if (git_repository_config__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +void git_repository_set_config(git_repository *repo, git_config *config) +{ + assert(repo && config); + + drop_config(repo); + + repo->_config = config; + GIT_REFCOUNT_OWN(repo->_config, repo); +} + +int git_repository_odb__weakptr(git_odb **out, git_repository *repo) +{ + assert(repo && out); + + if (repo->_odb == NULL) { + git_buf odb_path = GIT_BUF_INIT; + int res; + + if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0) + return -1; + + res = git_odb_open(&repo->_odb, odb_path.ptr); + git_buf_free(&odb_path); /* done with path */ + + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_odb, repo); + } + + *out = repo->_odb; + return 0; +} + +int git_repository_odb(git_odb **out, git_repository *repo) +{ + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +void git_repository_set_odb(git_repository *repo, git_odb *odb) +{ + assert(repo && odb); + + drop_odb(repo); + + repo->_odb = odb; + GIT_REFCOUNT_OWN(repo->_odb, repo); + GIT_REFCOUNT_INC(odb); +} + +int git_repository_index__weakptr(git_index **out, git_repository *repo) +{ + assert(out && repo); + + if (repo->_index == NULL) { + int res; + git_buf index_path = GIT_BUF_INIT; + + if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0) + return -1; + + res = git_index_open(&repo->_index, index_path.ptr); + git_buf_free(&index_path); /* done with path */ + + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_index, repo); + } + + *out = repo->_index; + return 0; +} + +int git_repository_index(git_index **out, git_repository *repo) +{ + if (git_repository_index__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +void git_repository_set_index(git_repository *repo, git_index *index) +{ + assert(repo && index); + + drop_index(repo); + + repo->_index = index; + GIT_REFCOUNT_OWN(repo->_index, repo); + GIT_REFCOUNT_INC(index); +} + +static int check_repositoryformatversion(git_repository *repo) +{ + git_config *config; + int version; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0) + return -1; + + if (GIT_REPO_VERSION < version) { + giterr_set(GITERR_REPOSITORY, + "Unsupported repository version %d. Only versions up to %d are supported.", + version, GIT_REPO_VERSION); + return -1; + } + + return 0; +} + +static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare) +{ + git_repository *repo = NULL; + + GIT_UNUSED(is_bare); + + if (git_repository_open(&repo, repository_path) < 0) + return -1; + + if (check_repositoryformatversion(repo) < 0) { + git_repository_free(repo); + return -1; + } + + /* TODO: reinitialize the templates */ + + *repo_out = repo; + return 0; +} + +static int repo_init_createhead(const char *git_dir) +{ + git_buf ref_path = GIT_BUF_INIT; + git_filebuf ref = GIT_FILEBUF_INIT; + + if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || + git_filebuf_open(&ref, ref_path.ptr, 0) < 0 || + git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 || + git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) + return -1; + + git_buf_free(&ref_path); + return 0; +} + +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; + + if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) + return false; + + if (p_stat(file_path, &st2) < 0) + return false; + + _is_supported = (st1.st_mode != st2.st_mode); + return _is_supported; +} + +static bool is_filesystem_case_insensitive(const char *gitdir_path) +{ + git_buf path = GIT_BUF_INIT; + static int _is_insensitive = -1; + + if (_is_insensitive > -1) + return _is_insensitive; + + 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; +} + +static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) +{ + git_buf cfg_path = GIT_BUF_INIT; + git_config *config = NULL; + +#define SET_REPO_CONFIG(type, name, val) {\ + if (git_config_set_##type(config, name, val) < 0) { \ + git_buf_free(&cfg_path); \ + git_config_free(config); \ + return -1; } \ +} + + if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; + + if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { + git_buf_free(&cfg_path); + return -1; + } + + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); + SET_REPO_CONFIG(bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); + + if (!is_reinit && is_filesystem_case_insensitive(git_dir)) + SET_REPO_CONFIG(bool, "core.ignorecase", true); + /* TODO: what other defaults? */ + + git_buf_free(&cfg_path); + git_config_free(config); + return 0; +} + +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0755 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0755 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0755 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0644 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0644 +#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" + +static int repo_write_template( + const char *git_dir, const char *file, mode_t mode, const char *content) +{ + git_buf path = GIT_BUF_INIT; + int fd, error = 0; + + if (git_buf_joinpath(&path, git_dir, file) < 0) + return -1; + + fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); + + if (fd >= 0) { + error = p_write(fd, content, strlen(content)); + + p_close(fd); + } + else if (errno != EEXIST) + error = fd; + + git_buf_free(&path); + + if (error) + giterr_set(GITERR_OS, + "Failed to initialize repository with template '%s'", file); + + return error; +} + +static int repo_init_structure(const char *git_dir, int is_bare) +{ + int i; + struct { const char *dir; mode_t mode; } dirs[] = { + { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */ + { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */ + { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */ + { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */ + { NULL, 0 } + }; + struct { const char *file; mode_t mode; const char *content; } tmpl[] = { + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } + }; + + /* Make the base directory */ + if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0) + return -1; + + /* Hides the ".git" directory */ + if (!is_bare) { +#ifdef GIT_WIN32 + if (p_hide_directory__w32(git_dir) < 0) { + giterr_set(GITERR_REPOSITORY, + "Failed to mark Git repository folder as hidden"); + return -1; + } +#endif + } + + /* Make subdirectories as needed */ + for (i = 0; dirs[i].dir != NULL; ++i) { + if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) + return -1; + } + + /* Make template files as needed */ + for (i = 0; tmpl[i].file != NULL; ++i) { + if (repo_write_template( + git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) + return -1; + } + + return 0; } int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) { - int error = GIT_SUCCESS; - git_repository *repo = NULL; - repo_init results; - + git_buf repository_path = GIT_BUF_INIT; + bool is_reinit; + int result = -1; + assert(repo_out && path); - results.path_repository = NULL; - results.is_bare = is_bare; - - error = repo_init_find_dir(&results, path); - if (error < GIT_SUCCESS) + if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) goto cleanup; - if (!repo_init_check_head_existence(results.path_repository)) - return repo_init_reinit(&results); + is_reinit = git_path_isdir(repository_path.ptr) && valid_repository_path(&repository_path); - error = repo_init_structure(&results); - if (error < GIT_SUCCESS) - goto cleanup; + if (is_reinit) { + if (repo_init_reinit(repo_out, repository_path.ptr, is_bare) < 0) + goto cleanup; - repo = repository_alloc(); - if (repo == NULL) { - error = GIT_ENOMEM; + result = repo_init_config(repository_path.ptr, is_bare, is_reinit); + } + + if (repo_init_structure(repository_path.ptr, is_bare) < 0 || + repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0 || + repo_init_createhead(repository_path.ptr) < 0 || + git_repository_open(repo_out, repository_path.ptr) < 0) { goto cleanup; } - error = guess_repository_dirs(repo, results.path_repository); - if (error < GIT_SUCCESS) - goto cleanup; - - assert(repo->is_bare == is_bare); - - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - error = repo_init_createhead(repo); - if (error < GIT_SUCCESS) - goto cleanup; - - /* should never fail */ - assert(check_repository_dirs(repo) == GIT_SUCCESS); - - free(results.path_repository); - *repo_out = repo; - return GIT_SUCCESS; + result = 0; cleanup: - free(results.path_repository); - git_repository_free(repo); - return git__rethrow(error, "Failed to (re)init the repository `%s`", path); + git_buf_free(&repository_path); + return result; +} + +int git_repository_head_detached(git_repository *repo) +{ + git_reference *ref; + git_odb *odb = NULL; + int exists; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + git_reference_free(ref); + return 0; + } + + exists = git_odb_exists(odb, git_reference_oid(ref)); + + git_reference_free(ref); + return exists; +} + +int git_repository_head(git_reference **head_out, git_repository *repo) +{ + return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); +} + +int git_repository_head_orphan(git_repository *repo) +{ + git_reference *ref = NULL; + int error; + + error = git_repository_head(&ref, repo); + git_reference_free(ref); + + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; + + return 0; } int git_repository_is_empty(git_repository *repo) { - git_reference *head, *branch; + git_reference *head = NULL, *branch = NULL; int error; - error = git_reference_lookup(&head, repo, "HEAD"); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to determine the emptiness of the repository. An error occured while retrieving the HEAD reference"); + if (git_reference_lookup(&head, repo, "HEAD") < 0) + return -1; - if (git_reference_type(head) != GIT_REF_SYMBOLIC) - return git__throw(GIT_EOBJCORRUPTED, "Failed to determine the emptiness of the repository. HEAD is probably in detached state"); + if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + git_reference_free(head); + return 0; + } - return git_reference_resolve(&branch, head) == GIT_SUCCESS ? 0 : 1; + if (strcmp(git_reference_target(head), "refs/heads/master") != 0) { + git_reference_free(head); + return 0; + } + + error = git_reference_resolve(&branch, head); + + git_reference_free(head); + git_reference_free(branch); + + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; + + return 0; } const char *git_repository_path(git_repository *repo) @@ -484,7 +950,27 @@ const char *git_repository_path(git_repository *repo) const char *git_repository_workdir(git_repository *repo) { assert(repo); - return repo->path_workdir; + + if (repo->is_bare) + return NULL; + + return repo->workdir; +} + +int git_repository_set_workdir(git_repository *repo, const char *workdir) +{ + git_buf path = GIT_BUF_INIT; + + assert(repo && workdir); + + if (git_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; + + git__free(repo->workdir); + + repo->workdir = git_buf_detach(&path); + repo->is_bare = 0; + return 0; } int git_repository_is_bare(git_repository *repo) @@ -492,3 +978,23 @@ int git_repository_is_bare(git_repository *repo) assert(repo); return repo->is_bare; } + +int git_repository_head_tree(git_tree **tree, git_repository *repo) +{ + git_oid head_oid; + git_object *obj = NULL; + + if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) { + /* cannot resolve HEAD - probably brand new repo */ + giterr_clear(); + *tree = NULL; + return 0; + } + + if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0 || + git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0) + return -1; + + *tree = (git_tree *)obj; + return 0; +} diff --git a/src/repository.h b/src/repository.h index bcf9b2bc8..91c69a655 100644 --- a/src/repository.h +++ b/src/repository.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_repository_h__ #define INCLUDE_repository_h__ @@ -7,16 +13,62 @@ #include "git2/repository.h" #include "git2/object.h" -#include "hashtable.h" #include "index.h" #include "cache.h" #include "refs.h" +#include "buffer.h" +#include "odb.h" +#include "attr.h" +#include "strmap.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" -#define GIT_OBJECTS_DIR "objects/" -#define GIT_INDEX_FILE "index" +#define GIT_DIR_MODE 0755 +#define GIT_BARE_DIR_MODE 0777 +/** Cvar cache identifiers */ +typedef enum { + GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */ + GIT_CVAR_EOL, /* core.eol */ + GIT_CVAR_CACHE_MAX +} git_cvar_cached; + +/** + * CVAR value enumerations + * + * These are the values that are actually stored in the cvar cache, instead + * of their string equivalents. These values are internal and symbolic; + * make sure that none of them is set to `-1`, since that is the unique + * identifier for "not cached" + */ +typedef enum { + /* The value hasn't been loaded from the cache yet */ + GIT_CVAR_NOT_CACHED = -1, + + /* core.safecrlf: false, 'fail', 'warn' */ + GIT_SAFE_CRLF_FALSE = 0, + GIT_SAFE_CRLF_FAIL = 1, + GIT_SAFE_CRLF_WARN = 2, + + /* core.autocrlf: false, true, 'input; */ + GIT_AUTO_CRLF_FALSE = 0, + GIT_AUTO_CRLF_TRUE = 1, + GIT_AUTO_CRLF_INPUT = 2, + GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, + + /* core.eol: unset, 'crlf', 'lf', 'native' */ + GIT_EOL_UNSET = 0, + GIT_EOL_CRLF = 1, + GIT_EOL_LF = 2, +#ifdef GIT_WIN32 + GIT_EOL_NATIVE = GIT_EOL_CRLF, +#else + GIT_EOL_NATIVE = GIT_EOL_LF, +#endif + GIT_EOL_DEFAULT = GIT_EOL_NATIVE +} git_cvar_value; + +/** Base git object for inheritance */ struct git_object { git_cached_obj cached; git_repository *repo; @@ -24,25 +76,63 @@ struct git_object { }; struct git_repository { - git_odb *db; + git_odb *_odb; + git_config *_config; + git_index *_index; git_cache objects; git_refcache references; + git_attr_cache attrcache; + git_strmap *submodules; char *path_repository; - char *path_index; - char *path_odb; - char *path_workdir; + char *workdir; unsigned is_bare:1; unsigned int lru_counter; + + git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; /* fully free the object; internal method, do not * export */ void git_object__free(void *object); -int git__parse_oid(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); -int git__write_oid(git_odb_stream *src, const char *header, const git_oid *oid); +int git_object__resolve_to_type(git_object **obj, git_otype type); + +int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); +void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); + +GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) +{ + return &repo->attrcache; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo); + +/* + * Weak pointers to repository internals. + * + * The returned pointers do not need to be freed. Do not keep + * permanent references to these (i.e. between API calls), since they may + * become invalidated if the user replaces a repository internal. + */ +int git_repository_config__weakptr(git_config **out, git_repository *repo); +int git_repository_odb__weakptr(git_odb **out, git_repository *repo); +int git_repository_index__weakptr(git_index **out, git_repository *repo); + +/* + * CVAR cache + * + * Efficient access to the most used config variables of a repository. + * The cache is cleared everytime the config backend is replaced. + */ +int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar); +void git_repository__cvar_cache_clear(git_repository *repo); + +/* + * Submodule cache + */ +extern void git_submodule_config_free(git_repository *repo); #endif diff --git a/src/revwalk.c b/src/revwalk.c index b64b0e2c0..e64d93f20 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -1,35 +1,28 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "commit.h" #include "odb.h" -#include "hashtable.h" #include "pqueue.h" +#include "pool.h" +#include "oidmap.h" #include "git2/revwalk.h" +#include "git2/merge.h" + +#include + +GIT__USE_OIDMAP; + +#define PARENT1 (1 << 0) +#define PARENT2 (1 << 1) +#define RESULT (1 << 2) +#define STALE (1 << 3) typedef struct commit_object { git_oid oid; @@ -37,7 +30,8 @@ typedef struct commit_object { unsigned int seen:1, uninteresting:1, topo_delay:1, - parsed:1; + parsed:1, + flags : 4; unsigned short in_degree; unsigned short out_degree; @@ -52,8 +46,10 @@ typedef struct commit_list { struct git_revwalk { git_repository *repo; + git_odb *odb; - git_hashtable *commits; + git_oidmap *commits; + git_pool commit_pool; commit_list *iterator_topo; commit_list *iterator_rand; @@ -63,47 +59,14 @@ struct git_revwalk { int (*get_next)(commit_object **, git_revwalk *); int (*enqueue)(git_revwalk *, commit_object *); - git_vector memory_alloc; - size_t chunk_size; - unsigned walking:1; unsigned int sorting; + + /* merge base calculation */ + commit_object *one; + git_vector twos; }; -commit_list *commit_list_insert(commit_object *item, commit_list **list_p) -{ - commit_list *new_list = git__malloc(sizeof(commit_list)); - new_list->item = item; - new_list->next = *list_p; - *list_p = new_list; - return new_list; -} - -void commit_list_free(commit_list **list_p) -{ - commit_list *list = *list_p; - - while (list) { - commit_list *temp = list; - list = temp->next; - free(temp); - } - - *list_p = NULL; -} - -commit_object *commit_list_pop(commit_list **stack) -{ - commit_list *top = *stack; - commit_object *item = top ? top->item : NULL; - - if (top) { - *stack = top->next; - free(top); - } - return item; -} - static int commit_time_cmp(void *a, void *b) { commit_object *commit_a = (commit_object *)a; @@ -112,61 +75,86 @@ static int commit_time_cmp(void *a, void *b) return (commit_a->time < commit_b->time); } -static uint32_t object_table_hash(const void *key, int hash_id) +static commit_list *commit_list_insert(commit_object *item, commit_list **list_p) { - uint32_t r; - git_oid *id; - - id = (git_oid *)key; - memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r)); - return r; + commit_list *new_list = git__malloc(sizeof(commit_list)); + if (new_list != NULL) { + new_list->item = item; + new_list->next = *list_p; + } + *list_p = new_list; + return new_list; } -#define COMMITS_PER_CHUNK 128 -#define CHUNK_STEP 64 -#define PARENTS_PER_COMMIT ((CHUNK_STEP - sizeof(commit_object)) / sizeof(commit_object *)) - -static int alloc_chunk(git_revwalk *walk) +static commit_list *commit_list_insert_by_date(commit_object *item, commit_list **list_p) { - void *chunk; + commit_list **pp = list_p; + commit_list *p; - chunk = git__calloc(COMMITS_PER_CHUNK, CHUNK_STEP); - if (chunk == NULL) - return GIT_ENOMEM; + while ((p = *pp) != NULL) { + if (commit_time_cmp(p->item, item) < 0) + break; - walk->chunk_size = 0; - return git_vector_insert(&walk->memory_alloc, chunk); + pp = &p->next; + } + + return commit_list_insert(item, pp); } +static void commit_list_free(commit_list **list_p) +{ + commit_list *list = *list_p; + + while (list) { + commit_list *temp = list; + list = temp->next; + git__free(temp); + } + + *list_p = NULL; +} + +static commit_object *commit_list_pop(commit_list **stack) +{ + commit_list *top = *stack; + commit_object *item = top ? top->item : NULL; + + if (top) { + *stack = top->next; + git__free(top); + } + return item; +} + +#define PARENTS_PER_COMMIT 2 +#define COMMIT_ALLOC \ + (sizeof(commit_object) + PARENTS_PER_COMMIT * sizeof(commit_object *)) static commit_object *alloc_commit(git_revwalk *walk) { - unsigned char *chunk; - - if (walk->chunk_size == COMMITS_PER_CHUNK) - alloc_chunk(walk); - - chunk = git_vector_get(&walk->memory_alloc, walk->memory_alloc.length - 1); - chunk += (walk->chunk_size * CHUNK_STEP); - walk->chunk_size++; - - return (commit_object *)chunk; + return (commit_object *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC); } -static commit_object **alloc_parents(commit_object *commit, size_t n_parents) +static commit_object **alloc_parents( + git_revwalk *walk, commit_object *commit, size_t n_parents) { if (n_parents <= PARENTS_PER_COMMIT) - return (commit_object **)((unsigned char *)commit + sizeof(commit_object)); + return (commit_object **)((char *)commit + sizeof(commit_object)); - return git__malloc(n_parents * sizeof(commit_object *)); + return (commit_object **)git_pool_malloc( + &walk->commit_pool, (uint32_t)(n_parents * sizeof(commit_object *))); } static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid) { commit_object *commit; + khiter_t pos; + int ret; - if ((commit = git_hashtable_lookup(walk->commits, oid)) != NULL) - return commit; + /* lookup and reserve space if not already present */ + pos = kh_get(oid, walk->commits, oid); + if (pos != kh_end(walk->commits)) + return kh_value(walk->commits, pos); commit = alloc_commit(walk); if (commit == NULL) @@ -174,66 +162,70 @@ static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid) git_oid_cpy(&commit->oid, oid); - if (git_hashtable_insert(walk->commits, &commit->oid, commit) < GIT_SUCCESS) { - free(commit); - return NULL; - } + pos = kh_put(oid, walk->commits, &commit->oid, &ret); + assert(ret != 0); + kh_value(walk->commits, pos) = commit; return commit; } static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw) { - const int parent_len = STRLEN("parent ") + GIT_OID_HEXSZ + 1; + const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1; unsigned char *buffer = raw->data; unsigned char *buffer_end = buffer + raw->len; unsigned char *parents_start; int i, parents = 0; - long commit_time; + int commit_time; - buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1; + buffer += strlen("tree ") + GIT_OID_HEXSZ + 1; parents_start = buffer; - while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", STRLEN("parent ")) == 0) { + while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) { parents++; buffer += parent_len; } - commit->parents = alloc_parents(commit, parents); - if (commit->parents == NULL) - return GIT_ENOMEM; + commit->parents = alloc_parents(walk, commit, parents); + GITERR_CHECK_ALLOC(commit->parents); buffer = parents_start; for (i = 0; i < parents; ++i) { git_oid oid; - if (git_oid_mkstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted"); + if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0) + return -1; commit->parents[i] = commit_lookup(walk, &oid); if (commit->parents[i] == NULL) - return GIT_ENOMEM; + return -1; buffer += parent_len; } commit->out_degree = (unsigned short)parents; - if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted"); + if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) { + giterr_set(GITERR_ODB, "Failed to parse commit. Object is corrupted"); + return -1; + } buffer = memchr(buffer, '>', buffer_end - buffer); - if (buffer == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author"); + if (buffer == NULL) { + giterr_set(GITERR_ODB, "Failed to parse commit. Can't find author"); + return -1; + } - if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time"); + if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < 0) { + giterr_set(GITERR_ODB, "Failed to parse commit. Can't parse commit time"); + return -1; + } commit->time = (time_t)commit_time; commit->parsed = 1; - return GIT_SUCCESS; + return 0; } static int commit_parse(git_revwalk *walk, commit_object *commit) @@ -242,19 +234,159 @@ static int commit_parse(git_revwalk *walk, commit_object *commit) int error; if (commit->parsed) - return GIT_SUCCESS; + return 0; - if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse commit. Can't read object"); + if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) + return error; if (obj->raw.type != GIT_OBJ_COMMIT) { - git_odb_object_close(obj); - return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object"); + git_odb_object_free(obj); + giterr_set(GITERR_INVALID, "Failed to parse commit. Object is no commit object"); + return -1; } error = commit_quick_parse(walk, commit, &obj->raw); - git_odb_object_close(obj); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse commit"); + git_odb_object_free(obj); + return error; +} + +static int interesting(git_pqueue *list) +{ + unsigned int i; + for (i = 1; i < git_pqueue_size(list); i++) { + commit_object *commit = list->d[i]; + if ((commit->flags & STALE) == 0) + return 1; + } + + return 0; +} + +static int merge_bases_many(commit_list **out, git_revwalk *walk, commit_object *one, git_vector *twos) +{ + int error; + unsigned int i; + commit_object *two; + commit_list *result = NULL, *tmp = NULL; + git_pqueue list; + + /* if the commit is repeated, we have a our merge base already */ + git_vector_foreach(twos, i, two) { + if (one == two) + return commit_list_insert(one, out) ? 0 : -1; + } + + if (git_pqueue_init(&list, twos->length * 2, commit_time_cmp) < 0) + return -1; + + if (commit_parse(walk, one) < 0) + return -1; + + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + return -1; + + git_vector_foreach(twos, i, two) { + commit_parse(walk, two); + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + return -1; + } + + /* as long as there are non-STALE commits */ + while (interesting(&list)) { + commit_object *commit; + int flags; + + commit = git_pqueue_pop(&list); + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) { + commit->flags |= RESULT; + if (commit_list_insert(commit, &result) == NULL) + return -1; + } + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + commit_object *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + + if ((error = commit_parse(walk, p)) < 0) + return error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + return -1; + } + } + + git_pqueue_free(&list); + + /* filter out any stale commits in the results */ + tmp = result; + result = NULL; + + while (tmp) { + struct commit_list *next = tmp->next; + if (!(tmp->item->flags & STALE)) + if (commit_list_insert_by_date(tmp->item, &result) == NULL) + return -1; + + git__free(tmp); + tmp = next; + } + + *out = result; + return 0; +} + +int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two) +{ + git_revwalk *walk; + git_vector list; + commit_list *result = NULL; + commit_object *commit; + void *contents[1]; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit = commit_lookup(walk, two); + if (commit == NULL) + goto on_error; + + /* This is just one value, so we can do it on the stack */ + memset(&list, 0x0, sizeof(git_vector)); + contents[0] = commit; + list.length = 1; + list.contents = contents; + + commit = commit_lookup(walk, one); + if (commit == NULL) + goto on_error; + + if (merge_bases_many(&result, walk, commit, &list) < 0) + goto on_error; + + if (!result) { + git_revwalk_free(walk); + return GIT_ENOTFOUND; + } + + git_oid_cpy(out, &result->item->oid); + commit_list_free(&result); + git_revwalk_free(walk); + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; } static void mark_uninteresting(commit_object *commit) @@ -264,25 +396,29 @@ static void mark_uninteresting(commit_object *commit) commit->uninteresting = 1; + /* This means we've reached a merge base, so there's no need to walk any more */ + if ((commit->flags & (RESULT | STALE)) == RESULT) + return; + for (i = 0; i < commit->out_degree; ++i) if (!commit->parents[i]->uninteresting) mark_uninteresting(commit->parents[i]); } -static int process_commit(git_revwalk *walk, commit_object *commit) +static int process_commit(git_revwalk *walk, commit_object *commit, int hide) { int error; + if (hide) + mark_uninteresting(commit); + if (commit->seen) - return GIT_SUCCESS; + return 0; commit->seen = 1; - if ((error = commit_parse(walk, commit)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to process commit"); - - if (commit->uninteresting) - mark_uninteresting(commit); + if ((error = commit_parse(walk, commit)) < 0) + return error; return walk->enqueue(walk, commit); } @@ -290,13 +426,12 @@ static int process_commit(git_revwalk *walk, commit_object *commit) static int process_commit_parents(git_revwalk *walk, commit_object *commit) { unsigned short i; - int error = GIT_SUCCESS; + int error = 0; - for (i = 0; i < commit->out_degree && error == GIT_SUCCESS; ++i) { - error = process_commit(walk, commit->parents[i]); - } + for (i = 0; i < commit->out_degree && !error; ++i) + error = process_commit(walk, commit->parents[i], commit->uninteresting); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to process commit parents"); + return error; } static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) @@ -305,11 +440,17 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) commit = commit_lookup(walk, oid); if (commit == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found"); + return -1; /* error already reported by failed lookup */ commit->uninteresting = uninteresting; + if (walk->one == NULL && !uninteresting) { + walk->one = commit; + } else { + if (git_vector_insert(&walk->twos, commit) < 0) + return -1; + } - return process_commit(walk, commit); + return 0; } int git_revwalk_push(git_revwalk *walk, const git_oid *oid) @@ -318,12 +459,122 @@ int git_revwalk_push(git_revwalk *walk, const git_oid *oid) return push_commit(walk, oid, 0); } + int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) { assert(walk && oid); return push_commit(walk, oid, 1); } +static int push_ref(git_revwalk *walk, const char *refname, int hide) +{ + git_oid oid; + + if (git_reference_name_to_oid(&oid, walk->repo, refname) < 0) + return -1; + + return push_commit(walk, &oid, hide); +} + +struct push_cb_data { + git_revwalk *walk; + const char *glob; + int hide; +}; + +static int push_glob_cb(const char *refname, void *data_) +{ + struct push_cb_data *data = (struct push_cb_data *)data_; + + if (!p_fnmatch(data->glob, refname, 0)) + return push_ref(data->walk, refname, data->hide); + + return 0; +} + +static int push_glob(git_revwalk *walk, const char *glob, int hide) +{ + git_buf buf = GIT_BUF_INIT; + struct push_cb_data data; + regex_t preg; + + 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 { + 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; + + data.walk = walk; + data.glob = git_buf_cstr(&buf); + data.hide = hide; + + if (git_reference_foreach( + walk->repo, GIT_REF_LISTALL, push_glob_cb, &data) < 0) + goto on_error; + + regfree(&preg); + git_buf_free(&buf); + return 0; + +on_error: + regfree(&preg); + git_buf_free(&buf); + return -1; +} + +int git_revwalk_push_glob(git_revwalk *walk, const char *glob) +{ + assert(walk && glob); + return push_glob(walk, glob, 0); +} + +int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) +{ + assert(walk && glob); + return push_glob(walk, glob, 1); +} + +int git_revwalk_push_head(git_revwalk *walk) +{ + assert(walk); + return push_ref(walk, GIT_HEAD_FILE, 0); +} + +int git_revwalk_hide_head(git_revwalk *walk) +{ + assert(walk); + return push_ref(walk, GIT_HEAD_FILE, 1); +} + +int git_revwalk_push_ref(git_revwalk *walk, const char *refname) +{ + assert(walk && refname); + return push_ref(walk, refname, 0); +} + +int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) +{ + assert(walk && refname); + return push_ref(walk, refname, 1); +} + static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit) { return git_pqueue_insert(&walk->iterator_time, commit); @@ -331,7 +582,7 @@ static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit) static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit) { - return commit_list_insert(commit, &walk->iterator_rand) ? GIT_SUCCESS : GIT_ENOMEM; + return commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; } static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) @@ -340,16 +591,16 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) commit_object *next; while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { - if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = process_commit_parents(walk, next)) < 0) + return error; if (!next->uninteresting) { *object_out = next; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_REVWALKOVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -358,16 +609,16 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) commit_object *next; while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) { - if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = process_commit_parents(walk, next)) < 0) + return error; if (!next->uninteresting) { *object_out = next; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_REVWALKOVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -378,7 +629,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); if (next == NULL) - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_REVWALKOVER; if (next->in_degree > 0) { next->topo_delay = 1; @@ -390,58 +641,83 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) if (--parent->in_degree == 0 && parent->topo_delay) { parent->topo_delay = 0; - commit_list_insert(parent, &walk->iterator_topo); + if (commit_list_insert(parent, &walk->iterator_topo) == NULL) + return -1; } } *object_out = next; - return GIT_SUCCESS; + return 0; } } static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk) { *object_out = commit_list_pop(&walk->iterator_reverse); - return *object_out ? GIT_SUCCESS : GIT_EREVWALKOVER; + return *object_out ? 0 : GIT_REVWALKOVER; } static int prepare_walk(git_revwalk *walk) { int error; - commit_object *next; + unsigned int i; + commit_object *next, *two; + commit_list *bases = NULL; + + /* + * If walk->one is NULL, there were no positive references, + * so we know that the walk is already over. + */ + if (walk->one == NULL) + return GIT_REVWALKOVER; + + /* first figure out what the merge bases are */ + if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0) + return -1; + + commit_list_free(&bases); + if (process_commit(walk, walk->one, walk->one->uninteresting) < 0) + return -1; + + git_vector_foreach(&walk->twos, i, two) { + if (process_commit(walk, two, two->uninteresting) < 0) + return -1; + } if (walk->sorting & GIT_SORT_TOPOLOGICAL) { unsigned short i; - while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) { + while ((error = walk->get_next(&next, walk)) == 0) { for (i = 0; i < next->out_degree; ++i) { commit_object *parent = next->parents[i]; parent->in_degree++; } - commit_list_insert(next, &walk->iterator_topo); + if (commit_list_insert(next, &walk->iterator_topo) == NULL) + return -1; } - if (error != GIT_EREVWALKOVER) - return git__rethrow(error, "Failed to prepare revision walk"); + if (error != GIT_REVWALKOVER) + return error; walk->get_next = &revwalk_next_toposort; } if (walk->sorting & GIT_SORT_REVERSE) { - while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) - commit_list_insert(next, &walk->iterator_reverse); + while ((error = walk->get_next(&next, walk)) == 0) + if (commit_list_insert(next, &walk->iterator_reverse) == NULL) + return -1; - if (error != GIT_EREVWALKOVER) - return git__rethrow(error, "Failed to prepare revision walk"); + if (error != GIT_REVWALKOVER) + return error; walk->get_next = &revwalk_next_reverse; } walk->walking = 1; - return GIT_SUCCESS; + return 0; } @@ -453,60 +729,46 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) git_revwalk *walk; walk = git__malloc(sizeof(git_revwalk)); - if (walk == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(walk); memset(walk, 0x0, sizeof(git_revwalk)); - walk->commits = git_hashtable_alloc(64, - object_table_hash, - (git_hash_keyeq_ptr)git_oid_cmp); + walk->commits = git_oidmap_alloc(); + GITERR_CHECK_ALLOC(walk->commits); - if (walk->commits == NULL) { - free(walk); - return GIT_ENOMEM; - } - - git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp); - git_vector_init(&walk->memory_alloc, 8, NULL); - alloc_chunk(walk); + if (git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp) < 0 || + git_vector_init(&walk->twos, 4, NULL) < 0 || + git_pool_init(&walk->commit_pool, 1, + git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0) + return -1; walk->get_next = &revwalk_next_unsorted; walk->enqueue = &revwalk_enqueue_unsorted; walk->repo = repo; + if (git_repository_odb(&walk->odb, repo) < 0) { + git_revwalk_free(walk); + return -1; + } + *revwalk_out = walk; - return GIT_SUCCESS; + return 0; } void git_revwalk_free(git_revwalk *walk) { - unsigned int i; - const void *GIT_UNUSED(_unused); - commit_object *commit; - if (walk == NULL) return; git_revwalk_reset(walk); + git_odb_free(walk->odb); - /* if the parent has more than PARENTS_PER_COMMIT parents, - * we had to allocate a separate array for those parents. - * make sure it's being free'd */ - GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, { - if (commit->out_degree > PARENTS_PER_COMMIT) - free(commit->parents); - }); - - git_hashtable_free(walk->commits); + git_oidmap_free(walk->commits); + git_pool_clear(&walk->commit_pool); git_pqueue_free(&walk->iterator_time); - - for (i = 0; i < walk->memory_alloc.length; ++i) - free(git_vector_get(&walk->memory_alloc, i)); - - git_vector_free(&walk->memory_alloc); - free(walk); + git_vector_free(&walk->twos); + git__free(walk); } git_repository *git_revwalk_repository(git_revwalk *walk) @@ -541,38 +803,43 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) assert(walk && oid); if (!walk->walking) { - if ((error = prepare_walk(walk)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = prepare_walk(walk)) < 0) + return error; } error = walk->get_next(&next, walk); - if (error < GIT_SUCCESS) { - if (error == GIT_EREVWALKOVER) - git_revwalk_reset(walk); - return git__rethrow(error, "Failed to load next revision"); + + if (error == GIT_REVWALKOVER) { + git_revwalk_reset(walk); + return GIT_REVWALKOVER; } - git_oid_cpy(oid, &next->oid); - return GIT_SUCCESS; + if (!error) + git_oid_cpy(oid, &next->oid); + + return error; } void git_revwalk_reset(git_revwalk *walk) { - const void *GIT_UNUSED(_unused); commit_object *commit; assert(walk); - GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, + kh_foreach_value(walk->commits, commit, { commit->seen = 0; commit->in_degree = 0; commit->topo_delay = 0; - ); + commit->uninteresting = 0; + }); git_pqueue_clear(&walk->iterator_time); commit_list_free(&walk->iterator_topo); commit_list_free(&walk->iterator_rand); commit_list_free(&walk->iterator_reverse); walk->walking = 0; + + walk->one = NULL; + git_vector_clear(&walk->twos); } diff --git a/src/block-sha1/sha1.c b/src/sha1.c similarity index 86% rename from src/block-sha1/sha1.c rename to src/sha1.c index 8c1460102..8aaedeb8f 100644 --- a/src/block-sha1/sha1.c +++ b/src/sha1.c @@ -1,9 +1,8 @@ /* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. + * Copyright (C) 2009-2012 the libgit2 contributors * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -53,11 +52,11 @@ */ #if defined(__i386__) || defined(__x86_64__) - #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) #elif defined(__GNUC__) && defined(__arm__) - #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) #else - #define setW(x, val) (W(x) = (val)) + #define setW(x, val) (W(x) = (val)) #endif /* @@ -68,27 +67,27 @@ */ #if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) + defined(_M_IX86) || defined(_M_X64) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) -#define get_be32(p) ntohl(*(unsigned int *)(p)) +#define get_be32(p) ntohl(*(const unsigned int *)(p)) #define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) #else #define get_be32(p) ( \ - (*((unsigned char *)(p) + 0) << 24) | \ - (*((unsigned char *)(p) + 1) << 16) | \ - (*((unsigned char *)(p) + 2) << 8) | \ - (*((unsigned char *)(p) + 3) << 0) ) + (*((const unsigned char *)(p) + 0) << 24) | \ + (*((const unsigned char *)(p) + 1) << 16) | \ + (*((const unsigned char *)(p) + 2) << 8) | \ + (*((const unsigned char *)(p) + 3) << 0) ) #define put_be32(p, v) do { \ unsigned int __v = (v); \ *((unsigned char *)(p) + 0) = __v >> 24; \ *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) #endif @@ -107,11 +106,11 @@ E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ B = SHA_ROR(B, 2); } while (0) -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) -#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) { @@ -233,7 +232,7 @@ void git__blk_SHA1_Init(blk_SHA_CTX *ctx) ctx->H[4] = 0xc3d2e1f0; } -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len) { unsigned int lenW = ctx->size & 63; @@ -243,7 +242,7 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) if (lenW) { unsigned int left = 64 - lenW; if (len < left) - left = len; + left = (unsigned int)len; memcpy(lenW + (char *)ctx->W, data, left); lenW = (lenW + left) & 63; len -= left; diff --git a/src/block-sha1/sha1.h b/src/sha1.h similarity index 61% rename from src/block-sha1/sha1.h rename to src/sha1.h index 558d6aece..93a244d76 100644 --- a/src/block-sha1/sha1.h +++ b/src/sha1.h @@ -1,9 +1,8 @@ /* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. + * Copyright (C) 2009-2012 the libgit2 contributors * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ typedef struct { @@ -13,7 +12,7 @@ typedef struct { } blk_SHA_CTX; void git__blk_SHA1_Init(blk_SHA_CTX *ctx); -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); +void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len); void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); #define SHA_CTX blk_SHA_CTX diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index f4a3c42cc..096da1739 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -1,27 +1,8 @@ /* - * This file is basically taken from git code. - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 @@ -33,71 +14,71 @@ * Conventional binary search loop looks like this: * * unsigned lo, hi; - * do { - * unsigned mi = (lo + hi) / 2; - * int cmp = "entry pointed at by mi" minus "target"; - * if (!cmp) - * return (mi is the wanted one) - * if (cmp > 0) - * hi = mi; "mi is larger than target" - * else - * lo = mi+1; "mi is smaller than target" - * } while (lo < hi); + * do { + * unsigned mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); * * The invariants are: * * - When entering the loop, lo points at a slot that is never - * above the target (it could be at the target), hi points at a - * slot that is guaranteed to be above the target (it can never - * be at the target). + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). * * - We find a point 'mi' between lo and hi (mi could be the same - * as lo, but never can be as same as hi), and check if it hits - * the target. There are three cases: + * as lo, but never can be as same as hi), and check if it hits + * the target. There are three cases: * - * - if it is a hit, we are happy. + * - if it is a hit, we are happy. * - * - if it is strictly higher than the target, we set it to hi, - * and repeat the search. + * - if it is strictly higher than the target, we set it to hi, + * and repeat the search. * - * - if it is strictly lower than the target, we update lo to - * one slot after it, because we allow lo to be at the target. + * - if it is strictly lower than the target, we update lo to + * one slot after it, because we allow lo to be at the target. * - * If the loop exits, there is no matching entry. + * If the loop exits, there is no matching entry. * * When choosing 'mi', we do not have to take the "middle" but * anywhere in between lo and hi, as long as lo <= mi < hi is - * satisfied. When we somehow know that the distance between the + * satisfied. When we somehow know that the distance between the * target and lo is much shorter than the target and hi, we could * pick mi that is much closer to lo than the midway. * * Now, we can take advantage of the fact that SHA-1 is a good hash * function, and as long as there are enough entries in the table, we - * can expect uniform distribution. An entry that begins with for + * can expect uniform distribution. An entry that begins with for * example "deadbeef..." is much likely to appear much later than in - * the midway of the table. It can reasonably be expected to be near + * the midway of the table. It can reasonably be expected to be near * 87% (222/256) from the top of the table. * - * However, we do not want to pick "mi" too precisely. If the entry at + * However, we do not want to pick "mi" too precisely. If the entry at * the 87% in the above example turns out to be higher than the target * we are looking for, we would end up narrowing the search space down * only by 13%, instead of 50% we would get if we did a simple binary - * search. So we would want to hedge our bets by being less aggressive. + * search. So we would want to hedge our bets by being less aggressive. * * The table at "table" holds at least "nr" entries of "elem_size" - * bytes each. Each entry has the SHA-1 key at "key_offset". The - * table is sorted by the SHA-1 key of the entries. The caller wants + * bytes each. Each entry has the SHA-1 key at "key_offset". The + * table is sorted by the SHA-1 key of the entries. The caller wants * to find the entry with "key", and knows that the entry at "lo" is * not higher than the entry it is looking for, and that the entry at * "hi" is higher than the entry it is looking for. */ int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key) + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key) { - const unsigned char *base = table; + const unsigned char *base = (const unsigned char*)table; const unsigned char *hi_key, *lo_key; unsigned ofs_0; @@ -157,7 +138,7 @@ int sha1_entry_pos(const void *table, * end up narrowing the search space by a smaller * amount (i.e. the distance between 'mi' and 'hi') * than what we would have (i.e. about half of 'lo' - * and 'hi'). Hedge our bets to pick 'mi' less + * and 'hi'). Hedge our bets to pick 'mi' less * aggressively, i.e. make 'mi' a bit closer to the * middle than we would otherwise pick. */ @@ -173,11 +154,12 @@ int sha1_entry_pos(const void *table, #ifdef INDEX_DEBUG_LOOKUP printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi); printf("ofs %u lov %x, hiv %x, kyv %x\n", - ofs_0, lov, hiv, kyv); + ofs_0, lov, hiv, kyv); #endif if (!(lo <= mi && mi < hi)) { - return git__throw(GIT_ERROR, "Assertion failure. Binary search invariant is false"); + giterr_set(GITERR_INVALID, "Assertion failure. Binary search invariant is false"); + return -1; } mi_key = base + elem_size * mi + key_offset; @@ -192,5 +174,5 @@ int sha1_entry_pos(const void *table, lo_key = mi_key + elem_size; } } while (lo < hi); - return -lo-1; + return -((int)lo)-1; } diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h index 5caa2f5ed..cd40a9d57 100644 --- a/src/sha1_lookup.h +++ b/src/sha1_lookup.h @@ -1,12 +1,18 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_sha1_lookup_h__ #define INCLUDE_sha1_lookup_h__ #include int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key); + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key); #endif diff --git a/src/signature.c b/src/signature.c index 51a2fff47..7d329c4c9 100644 --- a/src/signature.c +++ b/src/signature.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -33,48 +15,111 @@ void git_signature_free(git_signature *sig) if (sig == NULL) return; - free(sig->name); - free(sig->email); - free(sig); + git__free(sig->name); + sig->name = NULL; + git__free(sig->email); + sig->email = NULL; + git__free(sig); } -git_signature *git_signature_new(const char *name, const char *email, git_time_t time, int offset) +static const char *skip_leading_spaces(const char *buffer, const char *buffer_end) { + while (*buffer == ' ' && buffer < buffer_end) + buffer++; + + return buffer; +} + +static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end) +{ + while (*buffer_end == ' ' && buffer_end > buffer_start) + buffer_end--; + + return buffer_end; +} + +static int signature_error(const char *msg) +{ + giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg); + return -1; +} + +static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty) +{ + const char *left, *right; + size_t trimmed_input_length; + + assert(storage); + + left = skip_leading_spaces(input, input_end); + right = skip_trailing_spaces(input, input_end - 1); + + if (right < left) { + if (fail_when_empty) + return signature_error("input is either empty of contains only spaces"); + + right = left - 1; + } + + trimmed_input_length = right - left + 1; + + *storage = git__malloc(trimmed_input_length + 1); + GITERR_CHECK_ALLOC(*storage); + + memcpy(*storage, left, trimmed_input_length); + (*storage)[trimmed_input_length] = 0; + + return 0; +} + +int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) +{ + int error; git_signature *p = NULL; - if ((p = git__malloc(sizeof(git_signature))) == NULL) - goto cleanup; + assert(name && email); + + *sig_out = NULL; + + p = git__calloc(1, sizeof(git_signature)); + GITERR_CHECK_ALLOC(p); + + if ((error = process_trimming(name, &p->name, name + strlen(name), 1)) < 0 || + (error = process_trimming(email, &p->email, email + strlen(email), 1)) < 0) + { + git_signature_free(p); + return error; + } - p->name = git__strdup(name); - p->email = git__strdup(email); p->when.time = time; p->when.offset = offset; - if (p->name == NULL || p->email == NULL) - goto cleanup; + *sig_out = p; - return p; - -cleanup: - git_signature_free(p); - return NULL; + return 0; } git_signature *git_signature_dup(const git_signature *sig) { - return git_signature_new(sig->name, sig->email, sig->when.time, sig->when.offset); + git_signature *new; + if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < 0) + return NULL; + return new; } -git_signature *git_signature_now(const char *name, const char *email) +int git_signature_now(git_signature **sig_out, const char *name, const char *email) { time_t now; time_t offset; struct tm *utc_tm, *local_tm; + git_signature *sig; #ifndef GIT_WIN32 struct tm _utc, _local; #endif + *sig_out = NULL; + time(&now); /** @@ -96,163 +141,196 @@ git_signature *git_signature_now(const char *name, const char *email) if (local_tm->tm_isdst) offset += 60; - return git_signature_new(name, email, now, (int)offset); + if (git_signature_new(&sig, name, email, now, (int)offset) < 0) + return -1; + + *sig_out = sig; + + return 0; } -static int parse_timezone_offset(const char *buffer, long *offset_out) +static int timezone_error(const char *msg) { - long offset, dec_offset; - int mins, hours; + giterr_set(GITERR_INVALID, "Failed to parse TZ offset - %s", msg); + return -1; +} + +static int parse_timezone_offset(const char *buffer, int *offset_out) +{ + int dec_offset; + int mins, hours, offset; const char *offset_start; const char *offset_end; - //we are sure that *buffer == ' ' - offset_start = buffer + 1; + offset_start = buffer; if (*offset_start == '\n') { *offset_out = 0; - return GIT_SUCCESS; + return 0; } if (offset_start[0] != '-' && offset_start[0] != '+') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'"); + return timezone_error("does not start with '+' or '-'"); if (offset_start[1] < '0' || offset_start[1] > '9') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset."); + return timezone_error("expected initial digit"); - if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number"); + if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < 0) + return timezone_error("not a valid number"); if (offset_end - offset_start != 5) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length"); + return timezone_error("invalid length"); if (dec_offset > 1400) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Value too large"); + return timezone_error("value too large"); hours = dec_offset / 100; mins = dec_offset % 100; - if (hours > 14) // see http://www.worldtimezone.com/faq.html - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large"); + if (hours > 14) // see http://www.worldtimezone.com/faq.html + return timezone_error("hour value too large"); if (mins > 59) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large"); + return timezone_error("minutes value too large"); offset = (hours * 60) + mins; if (offset_start[0] == '-') offset *= -1; - + *offset_out = offset; - return GIT_SUCCESS; + return 0; } +static int process_next_token(const char **buffer_out, char **storage, + const char *token_end, const char *right_boundary) +{ + int error = process_trimming(*buffer_out, storage, token_end, 0); + if (error < 0) + return error; + + *buffer_out = token_end + 1; + + if (*buffer_out > right_boundary) + return signature_error("signature is too short"); + + return 0; +} + +static const char *scan_for_previous_token(const char *buffer, const char *left_boundary) +{ + const char *start; + + if (buffer <= left_boundary) + return NULL; + + start = skip_trailing_spaces(left_boundary, buffer); + + /* Search for previous occurence of space */ + while (start[-1] != ' ' && start > left_boundary) + start--; + + return start; +} + +static int parse_time(git_time_t *time_out, const char *buffer) +{ + int time; + int error; + + if (*buffer == '+' || *buffer == '-') { + giterr_set(GITERR_INVALID, "Failed while parsing time. '%s' actually looks like a timezone offset.", buffer); + return -1; + } + + error = git__strtol32(&time, buffer, &buffer, 10); + + if (!error) + *time_out = (git_time_t)time; + + return error; +} int git_signature__parse(git_signature *sig, const char **buffer_out, - const char *buffer_end, const char *header) + const char *buffer_end, const char *header, char ender) { - const size_t header_len = strlen(header); - - int name_length, email_length; const char *buffer = *buffer_out; - const char *line_end, *name_end, *email_end; - long offset = 0, time; + const char *line_end, *name_end, *email_end, *tz_start, *time_start; + int error = 0; memset(sig, 0x0, sizeof(git_signature)); - line_end = memchr(buffer, '\n', buffer_end - buffer); - if (!line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline found");; + if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) + return signature_error("no newline given"); - if (buffer + (header_len + 1) > line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); + if (header) { + const size_t header_len = strlen(header); - if (memcmp(buffer, header, header_len) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); + if (memcmp(buffer, header, header_len) != 0) + return signature_error("expected prefix doesn't match actual"); - buffer += header_len; + buffer += header_len; + } - /* Parse name */ - if ((name_end = strstr(buffer, " <")) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail start"); + if (buffer > line_end) + return signature_error("signature too short"); - name_length = name_end - buffer; - if (name_length <= 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Missing tagger name"); + if ((name_end = strchr(buffer, '<')) == NULL) + return signature_error("character '<' not allowed in signature"); - sig->name = git__malloc(name_length + 1); - if (sig->name == NULL) - return GIT_ENOMEM; + if ((email_end = strchr(name_end, '>')) == NULL) + return signature_error("character '>' not allowed in signature"); - memcpy(sig->name, buffer, name_length); - sig->name[name_length] = 0; - buffer = name_end + 2; + if (email_end < name_end) + return signature_error("malformed e-mail"); - if (buffer >= line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); + error = process_next_token(&buffer, &sig->name, name_end, line_end); + if (error < 0) + return error; - /* Parse email */ - if ((email_end = strstr(buffer, "> ")) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail end"); + error = process_next_token(&buffer, &sig->email, email_end, line_end); + if (error < 0) + return error; - email_length = email_end - buffer; - sig->email = git__malloc(email_length + 1); - if (sig->name == NULL) - return GIT_ENOMEM; + tz_start = scan_for_previous_token(line_end - 1, buffer); - memcpy(sig->email, buffer, email_length); - sig->email[email_length] = 0; - buffer = email_end + 2; + if (tz_start == NULL) + goto clean_exit; /* No timezone nor date */ - if (buffer >= line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); + time_start = scan_for_previous_token(tz_start - 1, buffer); + if (time_start == NULL || parse_time(&sig->when.time, time_start) < 0) { + /* The tz_start might point at the time */ + parse_time(&sig->when.time, tz_start); + goto clean_exit; + } - /* verify email */ - if (strpbrk(sig->email, "><\n ") != NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Malformed e-mail"); + if (parse_timezone_offset(tz_start, &sig->when.offset) < 0) { + sig->when.time = 0; /* Bogus timezone, we reset the time */ + } - if (git__strtol32(&time, buffer, &buffer, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Timestamp isn't a number"); - - sig->when.time = (time_t)time; - - if (parse_timezone_offset(buffer, &offset) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Could not parse timezone offset"); - - sig->when.offset = offset; - - *buffer_out = (line_end + 1); - return GIT_SUCCESS; +clean_exit: + *buffer_out = line_end + 1; + return 0; } -int git_signature__write(char **signature, const char *header, const git_signature *sig) +void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig) { int offset, hours, mins; - char sig_buffer[2048]; - int sig_buffer_len; char sign; offset = sig->when.offset; sign = (sig->when.offset < 0) ? '-' : '+'; - + if (offset < 0) offset = -offset; hours = offset / 60; mins = offset % 60; - sig_buffer_len = snprintf(sig_buffer, sizeof(sig_buffer), - "%s %s <%s> %u %c%02d%02d\n", - header, sig->name, sig->email, + git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n", + header ? header : "", sig->name, sig->email, (unsigned)sig->when.time, sign, hours, mins); - - if (sig_buffer_len < 0 || (size_t)sig_buffer_len > sizeof(sig_buffer)) - return GIT_ENOMEM; - - *signature = git__strdup(sig_buffer); - return sig_buffer_len; } - diff --git a/src/signature.h b/src/signature.h index feba6578d..97b3a055e 100644 --- a/src/signature.h +++ b/src/signature.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_signature_h__ #define INCLUDE_signature_h__ @@ -6,7 +12,7 @@ #include "repository.h" #include -int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header); -int git_signature__write(char **signature, const char *header, const git_signature *sig); +int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); +void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig); #endif diff --git a/src/status.c b/src/status.c new file mode 100644 index 000000000..e9ad3cfe4 --- /dev/null +++ b/src/status.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2.h" +#include "fileops.h" +#include "hash.h" +#include "vector.h" +#include "tree.h" +#include "git2/status.h" +#include "repository.h" +#include "ignore.h" + +#include "git2/diff.h" +#include "diff.h" + +static unsigned int index_delta2status(git_delta_t index_status) +{ + unsigned int st = GIT_STATUS_CURRENT; + + switch (index_status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + st = GIT_STATUS_INDEX_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_INDEX_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_INDEX_MODIFIED; + break; + default: + break; + } + + return st; +} + +static unsigned int workdir_delta2status(git_delta_t workdir_status) +{ + unsigned int st = GIT_STATUS_CURRENT; + + switch (workdir_status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + case GIT_DELTA_UNTRACKED: + st = GIT_STATUS_WT_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_WT_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_WT_MODIFIED; + break; + case GIT_DELTA_IGNORED: + st = GIT_STATUS_IGNORED; + break; + default: + break; + } + + return st; +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + int (*cb)(const char *, unsigned int, void *), + void *cbdata) +{ + int err = 0, cmp; + git_diff_options diffopt; + git_diff_list *idx2head = NULL, *wd2idx = NULL; + git_tree *head = NULL; + git_status_show_t show = + opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + git_diff_delta *i2h, *w2i; + unsigned int i, j, i_max, j_max; + + assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + + if ((err = git_repository_head_tree(&head, repo)) < 0) + return err; + + memset(&diffopt, 0, sizeof(diffopt)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; + if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + /* TODO: support EXCLUDE_SUBMODULES flag */ + + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && + (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0) + goto cleanup; + + if (show != GIT_STATUS_SHOW_INDEX_ONLY && + (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0) + goto cleanup; + + if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { + for (i = 0; !err && i < idx2head->deltas.length; i++) { + i2h = GIT_VECTOR_GET(&idx2head->deltas, i); + err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + } + git_diff_list_free(idx2head); + idx2head = NULL; + } + + i_max = idx2head ? idx2head->deltas.length : 0; + j_max = wd2idx ? wd2idx->deltas.length : 0; + + for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) { + i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; + w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; + + cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path); + + if (cmp < 0) { + err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + i++; + } else if (cmp > 0) { + err = cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata); + j++; + } else { + err = cb(i2h->old_file.path, index_delta2status(i2h->status) | + workdir_delta2status(w2i->status), cbdata); + i++; j++; + } + } + +cleanup: + git_tree_free(head); + git_diff_list_free(idx2head); + git_diff_list_free(wd2idx); + return err; +} + +int git_status_foreach( + git_repository *repo, + int (*callback)(const char *, unsigned int, void *), + void *payload) +{ + git_status_options opts; + + memset(&opts, 0, sizeof(opts)); + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + return git_status_foreach_ext(repo, &opts, callback, payload); +} + +struct status_file_info { + unsigned int count; + unsigned int status; + char *expected; +}; + +static int get_one_status(const char *path, unsigned int status, void *data) +{ + struct status_file_info *sfi = data; + + sfi->count++; + sfi->status = status; + + if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) { + giterr_set(GITERR_INVALID, + "Ambiguous path '%s' given to git_status_file", sfi->expected); + return -1; + } + + return 0; +} + +int git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path) +{ + int error; + git_status_options opts; + struct status_file_info sfi; + + assert(status_flags && repo && path); + + memset(&sfi, 0, sizeof(sfi)); + if ((sfi.expected = git__strdup(path)) == NULL) + return -1; + + memset(&opts, 0, sizeof(opts)); + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED; + opts.pathspec.count = 1; + opts.pathspec.strings = &sfi.expected; + + error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); + + if (!error && !sfi.count) { + giterr_set(GITERR_INVALID, + "Attempt to get status of nonexistent file '%s'", path); + error = GIT_ENOTFOUND; + } + + *status_flags = sfi.status; + + git__free(sfi.expected); + + return error; +} + +int git_status_should_ignore( + int *ignored, + git_repository *repo, + const char *path) +{ + int error; + git_ignores ignores; + + if (git_ignore__for_path(repo, path, &ignores) < 0) + return -1; + + error = git_ignore__lookup(&ignores, path, ignored); + git_ignore__free(&ignores); + return error; +} + diff --git a/src/strmap.h b/src/strmap.h new file mode 100644 index 000000000..da5ca0dba --- /dev/null +++ b/src/strmap.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_strmap_h__ +#define INCLUDE_strmap_h__ + +#include "common.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(str, const char *, void *); +typedef khash_t(str) git_strmap; + +#define GIT__USE_STRMAP \ + __KHASH_IMPL(str, static inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) + +#define git_strmap_alloc() kh_init(str) +#define git_strmap_free(h) kh_destroy(str, h), h = NULL +#define git_strmap_clear(h) kh_clear(str, h) + +#define git_strmap_num_entries(h) kh_size(h) + +#define git_strmap_lookup_index(h, k) kh_get(str, h, k) +#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_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) + +#define git_strmap_insert(h, key, val, rval) do { \ + khiter_t __pos = kh_put(str, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_strmap_insert2(h, key, val, oldv, rval) do { \ + khiter_t __pos = kh_put(str, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) { \ + oldv = kh_val(h, __pos); \ + kh_key(h, __pos) = key; \ + } else { oldv = NULL; } \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_strmap_delete(h, key) do { \ + khiter_t __pos = git_strmap_lookup_index(h, key); \ + if (git_strmap_valid_index(h, __pos)) \ + git_strmap_delete_at(h, __pos); } while (0) + +#define git_strmap_foreach kh_foreach +#define git_strmap_foreach_value kh_foreach_value + +#endif diff --git a/src/submodule.c b/src/submodule.c new file mode 100644 index 000000000..3c07e657d --- /dev/null +++ b/src/submodule.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2/config.h" +#include "git2/types.h" +#include "git2/repository.h" +#include "git2/index.h" +#include "git2/submodule.h" +#include "buffer.h" +#include "vector.h" +#include "posix.h" +#include "config_file.h" +#include "config.h" +#include "repository.h" + +static git_cvar_map _sm_update_map[] = { + {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, + {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, + {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE} +}; + +static git_cvar_map _sm_ignore_map[] = { + {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE} +}; + +static inline khint_t str_hash_no_trailing_slash(const char *s) +{ + khint_t h; + + for (h = 0; *s; ++s) + if (s[1] || *s != '/') + h = (h << 5) - h + *s; + + return h; +} + +static inline int str_equal_no_trailing_slash(const char *a, const char *b) +{ + size_t alen = a ? strlen(a) : 0; + size_t blen = b ? strlen(b) : 0; + + if (alen && a[alen] == '/') + alen--; + if (blen && b[blen] == '/') + blen--; + + return (alen == blen && strncmp(a, b, alen) == 0); +} + +__KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash); + +static git_submodule *submodule_alloc(const char *name) +{ + git_submodule *sm = git__calloc(1, sizeof(git_submodule)); + if (sm == NULL) + return sm; + + sm->path = sm->name = git__strdup(name); + if (!sm->name) { + git__free(sm); + return NULL; + } + + return sm; +} + +static void submodule_release(git_submodule *sm, int decr) +{ + if (!sm) + return; + + sm->refcount -= decr; + + if (sm->refcount == 0) { + if (sm->name != sm->path) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__free(sm); + } +} + +static int submodule_from_entry( + git_strmap *smcfg, git_index_entry *entry) +{ + git_submodule *sm; + void *old_sm; + khiter_t pos; + int error; + + pos = git_strmap_lookup_index(smcfg, entry->path); + + if (git_strmap_valid_index(smcfg, pos)) + sm = git_strmap_value_at(smcfg, pos); + else + sm = submodule_alloc(entry->path); + + git_oid_cpy(&sm->oid, &entry->oid); + + if (strcmp(sm->path, entry->path) != 0) { + if (sm->path != sm->name) { + git__free(sm->path); + sm->path = sm->name; + } + sm->path = git__strdup(entry->path); + if (!sm->path) + goto fail; + } + + git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + + if (old_sm && ((git_submodule *)old_sm) != sm) { + /* TODO: log warning about multiple entrys for same submodule path */ + submodule_release(old_sm, 1); + } + + return 0; + +fail: + submodule_release(sm, 0); + return -1; +} + +static int submodule_from_config( + const char *key, const char *value, void *data) +{ + git_strmap *smcfg = data; + const char *namestart; + const char *property; + git_buf name = GIT_BUF_INIT; + git_submodule *sm; + void *old_sm = NULL; + bool is_path; + khiter_t pos; + int error; + + if (git__prefixcmp(key, "submodule.") != 0) + return 0; + + namestart = key + strlen("submodule."); + property = strrchr(namestart, '.'); + if (property == NULL) + return 0; + property++; + is_path = (strcmp(property, "path") == 0); + + if (git_buf_set(&name, namestart, property - namestart - 1) < 0) + return -1; + + pos = git_strmap_lookup_index(smcfg, name.ptr); + if (!git_strmap_valid_index(smcfg, pos) && is_path) + pos = git_strmap_lookup_index(smcfg, value); + if (!git_strmap_valid_index(smcfg, pos)) + sm = submodule_alloc(name.ptr); + else + sm = git_strmap_value_at(smcfg, pos); + if (!sm) + goto fail; + + if (strcmp(sm->name, name.ptr) != 0) { + assert(sm->path == sm->name); + sm->name = git_buf_detach(&name); + + git_strmap_insert2(smcfg, sm->name, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + } + else if (is_path && strcmp(sm->path, value) != 0) { + assert(sm->path == sm->name); + sm->path = git__strdup(value); + if (sm->path == NULL) + goto fail; + + git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + } + git_buf_free(&name); + + if (old_sm && ((git_submodule *)old_sm) != sm) { + /* TODO: log warning about multiple submodules with same path */ + submodule_release(old_sm, 1); + } + + if (is_path) + return 0; + + /* copy other properties into submodule entry */ + if (strcmp(property, "url") == 0) { + if (sm->url) { + git__free(sm->url); + sm->url = NULL; + } + if ((sm->url = git__strdup(value)) == NULL) + goto fail; + } + else if (strcmp(property, "update") == 0) { + int val; + if (git_config_lookup_map_value( + _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule update property: '%s'", value); + goto fail; + } + sm->update = (git_submodule_update_t)val; + } + else if (strcmp(property, "fetchRecurseSubmodules") == 0) { + if (git__parse_bool(&sm->fetch_recurse, value) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); + goto fail; + } + } + else if (strcmp(property, "ignore") == 0) { + int val; + if (git_config_lookup_map_value( + _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule ignore property: '%s'", value); + goto fail; + } + sm->ignore = (git_submodule_ignore_t)val; + } + /* ignore other unknown submodule properties */ + + return 0; + +fail: + submodule_release(sm, 0); + git_buf_free(&name); + return -1; +} + +static int load_submodule_config(git_repository *repo) +{ + int error; + git_index *index; + unsigned int i, max_i; + git_oid gitmodules_oid; + git_strmap *smcfg; + struct git_config_file *mods = NULL; + + if (repo->submodules) + return 0; + + /* submodule data is kept in a hashtable with each submodule stored + * under both its name and its path. These are usually the same, but + * that is not guaranteed. + */ + smcfg = git_strmap_alloc(); + GITERR_CHECK_ALLOC(smcfg); + + /* scan index for gitmodules (and .gitmodules entry) */ + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + goto cleanup; + memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); + max_i = git_index_entrycount(index); + + for (i = 0; i < max_i; i++) { + git_index_entry *entry = git_index_get(index, i); + if (S_ISGITLINK(entry->mode)) { + if ((error = submodule_from_entry(smcfg, entry)) < 0) + goto cleanup; + } + else if (strcmp(entry->path, ".gitmodules") == 0) + git_oid_cpy(&gitmodules_oid, &entry->oid); + } + + /* load .gitmodules from workdir if it exists */ + if (git_repository_workdir(repo) != NULL) { + /* look in workdir for .gitmodules */ + git_buf path = GIT_BUF_INIT; + if (!git_buf_joinpath( + &path, git_repository_workdir(repo), ".gitmodules") && + git_path_isfile(path.ptr)) + { + if (!(error = git_config_file__ondisk(&mods, path.ptr))) + error = git_config_file_open(mods); + } + git_buf_free(&path); + } + + /* load .gitmodules from object cache if not in workdir */ + if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { + /* TODO: is it worth loading gitmodules from object cache? */ + } + + /* process .gitmodules info */ + if (!error && mods != NULL) + error = git_config_file_foreach(mods, submodule_from_config, smcfg); + + /* store submodule config in repo */ + if (!error) + repo->submodules = smcfg; + +cleanup: + if (mods != NULL) + git_config_file_free(mods); + if (error) + git_strmap_free(smcfg); + return error; +} + +void git_submodule_config_free(git_repository *repo) +{ + git_strmap *smcfg = repo->submodules; + git_submodule *sm; + + repo->submodules = NULL; + + if (smcfg == NULL) + return; + + git_strmap_foreach_value(smcfg, sm, { + submodule_release(sm,1); + }); + git_strmap_free(smcfg); +} + +static int submodule_cmp(const void *a, const void *b) +{ + return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); +} + +int git_submodule_foreach( + git_repository *repo, + int (*callback)(const char *name, void *payload), + void *payload) +{ + int error; + git_submodule *sm; + git_vector seen = GIT_VECTOR_INIT; + seen._cmp = submodule_cmp; + + if ((error = load_submodule_config(repo)) < 0) + return error; + + git_strmap_foreach_value(repo->submodules, sm, { + /* usually the following will not come into play */ + if (sm->refcount > 1) { + if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) + continue; + if ((error = git_vector_insert(&seen, sm)) < 0) + break; + } + + if ((error = callback(sm->name, payload)) < 0) + break; + }); + + git_vector_free(&seen); + + return error; +} + +int git_submodule_lookup( + git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + khiter_t pos; + + if (load_submodule_config(repo) < 0) + return -1; + + pos = git_strmap_lookup_index(repo->submodules, name); + if (!git_strmap_valid_index(repo->submodules, pos)) + return GIT_ENOTFOUND; + + if (sm_ptr) + *sm_ptr = git_strmap_value_at(repo->submodules, pos); + + return 0; +} diff --git a/src/t03-data.h b/src/t03-data.h deleted file mode 100644 index a4b73fec3..000000000 --- a/src/t03-data.h +++ /dev/null @@ -1,344 +0,0 @@ - -typedef struct object_data { - char *id; /* object id (sha1) */ - char *dir; /* object store (fan-out) directory name */ - char *file; /* object store filename */ -} object_data; - -static object_data commit = { - "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", - "test-objects/3d", - "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", -}; - -static unsigned char commit_data[] = { - 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, - 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, - 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, - 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, - 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, - 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, - 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, - 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, - 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, - 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, - 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, - 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, - 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, - 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x3e, 0x0a, -}; - -static git_rawobj commit_obj = { - commit_data, - sizeof(commit_data), - GIT_OBJ_COMMIT -}; - -static object_data tree = { - "dff2da90b254e1beb889d1f1f1288be1803782df", - "test-objects/df", - "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", -}; - -static unsigned char tree_data[] = { - 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, - 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, - 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, - 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, - 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, - 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, - 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, - 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, - 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, - 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, - 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, - 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, - 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, - 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, -}; - -static git_rawobj tree_obj = { - tree_data, - sizeof(tree_data), - GIT_OBJ_TREE -}; - -static object_data tag = { - "09d373e1dfdc16b129ceec6dd649739911541e05", - "test-objects/09", - "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", -}; - -static unsigned char tag_data[] = { - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, - 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, - 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, - 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, - 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, - 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, - 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, - 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x0a, -}; - -static git_rawobj tag_obj = { - tag_data, - sizeof(tag_data), - GIT_OBJ_TAG -}; - -static object_data zero = { - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - "test-objects/e6", - "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", -}; - -static unsigned char zero_data[] = { - 0x00 /* dummy data */ -}; - -static git_rawobj zero_obj = { - zero_data, - 0, - GIT_OBJ_BLOB -}; - -static object_data one = { - "8b137891791fe96927ad78e64b0aad7bded08bdc", - "test-objects/8b", - "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", -}; - -static unsigned char one_data[] = { - 0x0a, -}; - -static git_rawobj one_obj = { - one_data, - sizeof(one_data), - GIT_OBJ_BLOB -}; - -static object_data two = { - "78981922613b2afb6025042ff6bd878ac1994e85", - "test-objects/78", - "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", -}; - -static unsigned char two_data[] = { - 0x61, 0x0a, -}; - -static git_rawobj two_obj = { - two_data, - sizeof(two_data), - GIT_OBJ_BLOB -}; - -static object_data some = { - "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", - "test-objects/fd", - "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", -}; - -static unsigned char some_data[] = { - 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, - 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, - 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, - 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, - 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, - 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, - 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, - 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, - 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, - 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, - 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, - 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, - 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, - 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, - 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, - 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, - 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, - 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, - 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, - 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, - 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, - 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, - 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, - 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, - 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, - 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, - 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, - 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, - 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, - 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, - 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, - 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, - 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, - 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, - 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, - 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, - 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, - 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, - 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, - 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, - 0x0a, -}; - -static git_rawobj some_obj = { - some_data, - sizeof(some_data), - GIT_OBJ_BLOB -}; diff --git a/src/tag.c b/src/tag.c index 905d129ef..63424f530 100644 --- a/src/tag.c +++ b/src/tag.c @@ -1,32 +1,15 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "commit.h" #include "tag.h" #include "signature.h" +#include "message.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/signature.h" @@ -34,9 +17,9 @@ void git_tag__free(git_tag *tag) { git_signature_free(tag->tagger); - free(tag->message); - free(tag->tag_name); - free(tag); + git__free(tag->message); + git__free(tag->tag_name); + git__free(tag); } const git_oid *git_tag_id(git_tag *c) @@ -79,24 +62,32 @@ const char *git_tag_message(git_tag *t) return t->message; } -static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer_end) +static int tag_error(const char *str) +{ + giterr_set(GITERR_TAG, "Failed to parse tag. %s", str); + return -1; +} + +int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) { static const char *tag_types[] = { NULL, "commit\n", "tree\n", "blob\n", "tag\n" }; - unsigned int i, text_len; + unsigned int i; + size_t text_len; char *search; - int error; - if ((error = git__parse_oid(&tag->target, &buffer, buffer_end, "object ")) < 0) - return git__rethrow(error, "Failed to parse tag. Object field invalid"); + const char *buffer_end = buffer + length; + + if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) + return tag_error("Object field invalid"); if (buffer + 5 >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, "type ", 5) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Type field not found"); + return tag_error("Type field not found"); buffer += 5; tag->type = GIT_OBJ_BAD; @@ -105,7 +96,7 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer size_t type_length = strlen(tag_types[i]); if (buffer + type_length >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, tag_types[i], type_length) == 0) { tag->type = i; @@ -115,192 +106,89 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer } if (tag->type == GIT_OBJ_BAD) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Invalid object type"); + return tag_error("Invalid object type"); if (buffer + 4 >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, "tag ", 4) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Tag field not found"); + return tag_error("Tag field not found"); buffer += 4; search = memchr(buffer, '\n', buffer_end - buffer); if (search == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); text_len = search - buffer; tag->tag_name = git__malloc(text_len + 1); - if (tag->tag_name == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(tag->tag_name); memcpy(tag->tag_name, buffer, text_len); tag->tag_name[text_len] = '\0'; buffer = search + 1; - tag->tagger = git__malloc(sizeof(git_signature)); - if (tag->tagger == NULL) - return GIT_ENOMEM; + tag->tagger = NULL; + if (*buffer != '\n') { + tag->tagger = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(tag->tagger); - if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ")) != 0) { - free(tag->tag_name); - git_signature_free(tag->tagger); - return git__rethrow(error, "Failed to parse tag"); + if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0) + return -1; } if( *buffer != '\n' ) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. No new line before message"); + return tag_error("No new line before message"); text_len = buffer_end - ++buffer; tag->message = git__malloc(text_len + 1); - if (tag->message == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(tag->message); memcpy(tag->message, buffer, text_len); tag->message[text_len] = '\0'; - return GIT_SUCCESS; + return 0; } -static int retreive_tag_reference(git_reference **tag_reference_out, char *ref_name_out, git_repository *repo, const char *tag_name) +static int retrieve_tag_reference( + git_reference **tag_reference_out, + git_buf *ref_name_out, + git_repository *repo, + const char *tag_name) { git_reference *tag_ref; int error; - git__joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name); - error = git_reference_lookup(&tag_ref, repo, ref_name_out); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to retrieve tag reference"); + *tag_reference_out = NULL; + + if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); + if (error < 0) + return error; /* Be it not foundo or corrupted */ *tag_reference_out = tag_ref; - return GIT_SUCCESS; + return 0; } -static int tag_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_oid *target, - git_otype target_type, - const git_signature *tagger, - const char *message, - int allow_ref_overwrite) +static int retrieve_tag_reference_oid( + git_oid *oid, + git_buf *ref_name_out, + git_repository *repo, + const char *tag_name) { - size_t final_size = 0; - git_odb_stream *stream; + if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; - const char *type_str; - char *tagger_str; - git_reference *new_ref; - - char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - - int type_str_len, tag_name_len, tagger_str_len, message_len; - int error, should_update_ref = 0; - - /** Ensure the tag name doesn't conflict with an already existing - reference unless overwriting has explictly been requested **/ - error = retreive_tag_reference(&new_ref, ref_name, repo, tag_name); - - switch (error) { - case GIT_SUCCESS: - if (!allow_ref_overwrite) - return GIT_EEXISTS; - should_update_ref = 1; - - /* Fall trough */ - - case GIT_ENOTFOUND: - break; - - default: - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); - } - - if (!git_odb_exists(repo->db, target)) - return git__throw(GIT_ENOTFOUND, "Failed to create tag. Object to tag doesn't exist"); - - /* Try to find out what the type is */ - if (target_type == GIT_OBJ_ANY) { - size_t _unused; - error = git_odb_read_header(&_unused, &target_type, repo->db, target); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - } - - type_str = git_object_type2string(target_type); - - tagger_str_len = git_signature__write(&tagger_str, "tagger", tagger); - - type_str_len = strlen(type_str); - tag_name_len = strlen(tag_name); - message_len = strlen(message); - - final_size += GIT_OID_LINE_LENGTH("object"); - final_size += STRLEN("type ") + type_str_len + 1; - final_size += STRLEN("tag ") + tag_name_len + 1; - final_size += tagger_str_len; - final_size += 1 + message_len; - - if ((error = git_odb_open_wstream(&stream, repo->db, final_size, GIT_OBJ_TAG)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - - git__write_oid(stream, "object", target); - - stream->write(stream, "type ", STRLEN("type ")); - stream->write(stream, type_str, type_str_len); - - stream->write(stream, "\ntag ", STRLEN("\ntag ")); - stream->write(stream, tag_name, tag_name_len); - stream->write(stream, "\n", 1); - - stream->write(stream, tagger_str, tagger_str_len); - free(tagger_str); - - stream->write(stream, "\n", 1); - stream->write(stream, message, message_len); - - - error = stream->finalize_write(oid, stream); - stream->free(stream); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - - if (!should_update_ref) - error = git_reference_create_oid(&new_ref, repo, ref_name, oid); - else - error = git_reference_set_oid(new_ref, oid); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); + return git_reference_name_to_oid(oid, repo, ref_name_out->ptr); } -int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer) -{ - git_tag tag; - int error; - - assert(oid && buffer); - - memset(&tag, 0, sizeof(tag)); - - if ((error = parse_tag_buffer(&tag, buffer, buffer + strlen(buffer))) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - - error = git_tag_create(oid, repo, tag.tag_name, &tag.target, tag.type, tag.tagger, tag.message); - - git_signature_free(tag.tagger); - free(tag.tag_name); - free(tag.message); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); -} - -int git_tag_create_o( +static int write_tag_annotation( git_oid *oid, git_repository *repo, const char *tag_name, @@ -308,69 +196,199 @@ int git_tag_create_o( const git_signature *tagger, const char *message) { - return tag_create( - oid, repo, tag_name, - git_object_id(target), - git_object_type(target), - tagger, message, 0); + git_buf tag = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT; + git_odb *odb; + + git_oid__writebuf(&tag, "object ", git_object_id(target)); + git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); + git_buf_printf(&tag, "tag %s\n", tag_name); + git_signature__writebuf(&tag, "tagger ", tagger); + git_buf_putc(&tag, '\n'); + + /* Remove comments by default */ + if (git_message_prettify(&cleaned_message, message, 1) < 0) + goto on_error; + + if (git_buf_puts(&tag, git_buf_cstr(&cleaned_message)) < 0) + goto on_error; + + git_buf_free(&cleaned_message); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; + + if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0) + goto on_error; + + git_buf_free(&tag); + return 0; + +on_error: + git_buf_free(&tag); + git_buf_free(&cleaned_message); + giterr_set(GITERR_OBJECT, "Failed to create tag annotation."); + return -1; +} + +static int git_tag_create__internal( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite, + int create_tag_annotation) +{ + git_reference *new_ref = NULL; + git_buf ref_name = GIT_BUF_INIT; + + int error; + + assert(repo && tag_name && target); + assert(!create_tag_annotation || (tagger && message)); + + if (git_object_owner(target) != repo) { + giterr_set(GITERR_INVALID, "The given target does not belong to this repository"); + return -1; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + return -1; + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explictly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_buf_free(&ref_name); + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; + } + + if (create_tag_annotation) { + if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) + return -1; + } else + git_oid_cpy(oid, git_object_id(target)); + + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); + + git_reference_free(new_ref); + git_buf_free(&ref_name); + return error; } int git_tag_create( git_oid *oid, git_repository *repo, const char *tag_name, - const git_oid *target, - git_otype target_type, + const git_object *target, const git_signature *tagger, - const char *message) + const char *message, + int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - target, - target_type, - tagger, message, 0); + return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); } -int git_tag_create_fo( +int git_tag_create_lightweight( git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, - const git_signature *tagger, - const char *message) + int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - git_object_id(target), - git_object_type(target), - tagger, message, 1); + return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); } -int git_tag_create_f( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_oid *target, - git_otype target_type, - const git_signature *tagger, - const char *message) +int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - target, - target_type, - tagger, message, 1); + git_tag tag; + int error; + git_odb *odb; + git_odb_stream *stream; + git_odb_object *target_obj; + + git_reference *new_ref = NULL; + git_buf ref_name = GIT_BUF_INIT; + + assert(oid && buffer); + + memset(&tag, 0, sizeof(tag)); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + /* validate the buffer */ + if (git_tag__parse_buffer(&tag, buffer, strlen(buffer)) < 0) + return -1; + + /* validate the target */ + if (git_odb_read(&target_obj, odb, &tag.target) < 0) + goto on_error; + + if (tag.type != target_obj->raw.type) { + giterr_set(GITERR_TAG, "The type for the given target is invalid"); + goto on_error; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + /* We don't need these objects after this */ + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explictly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; + } + + /* write the buffer */ + if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) + return -1; + + stream->write(stream, buffer, strlen(buffer)); + + error = stream->finalize_write(oid, stream); + stream->free(stream); + + if (error < 0) { + git_buf_free(&ref_name); + return -1; + } + + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); + + git_reference_free(new_ref); + git_buf_free(&ref_name); + + return error; + +on_error: + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + return -1; } int git_tag_delete(git_repository *repo, const char *tag_name) { int error; git_reference *tag_ref; - char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + git_buf ref_name = GIT_BUF_INIT; - error = retreive_tag_reference(&tag_ref, ref_name, repo, tag_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete tag"); + error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); + + git_buf_free(&ref_name); + + if (error < 0) + return -1; return git_reference_delete(tag_ref); } @@ -378,32 +396,76 @@ int git_tag_delete(git_repository *repo, const char *tag_name) int git_tag__parse(git_tag *tag, git_odb_object *obj) { assert(tag); - return parse_tag_buffer(tag, obj->raw.data, (char *)obj->raw.data + obj->raw.len); + return git_tag__parse_buffer(tag, obj->raw.data, obj->raw.len); } +typedef struct { + git_vector *taglist; + const char *pattern; +} tag_filter_data; + +#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) + static int tag_list_cb(const char *tag_name, void *payload) { - if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) == 0) - return git_vector_insert((git_vector *)payload, git__strdup(tag_name)); + tag_filter_data *filter; - return GIT_SUCCESS; + if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0) + return 0; + + filter = (tag_filter_data *)payload; + if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) + return git_vector_insert(filter->taglist, git__strdup(tag_name)); + + return 0; } -int git_tag_list(git_strarray *tag_names, git_repository *repo) +int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) { int error; + tag_filter_data filter; git_vector taglist; - if (git_vector_init(&taglist, 8, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; + assert(tag_names && repo && pattern); - error = git_reference_listcb(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&taglist); - if (error < GIT_SUCCESS) { + if (git_vector_init(&taglist, 8, NULL) < 0) + return -1; + + filter.taglist = &taglist; + filter.pattern = pattern; + + error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter); + if (error < 0) { git_vector_free(&taglist); - return git__rethrow(error, "Failed to list tags"); + return -1; } tag_names->strings = (char **)taglist.contents; tag_names->count = taglist.length; - return GIT_SUCCESS; + return 0; +} + +int git_tag_list(git_strarray *tag_names, git_repository *repo) +{ + return git_tag_list_match(tag_names, "", repo); +} + +int git_tag_peel(git_object **tag_target, git_tag *tag) +{ + int error; + git_object *target; + + assert(tag_target && tag); + + if (git_tag_target(&target, tag) < 0) + return -1; + + if (git_object_type(target) == GIT_OBJ_TAG) { + error = git_tag_peel(tag_target, (git_tag *)target); + git_object_free(target); + return error; + } + + *tag_target = target; + return 0; } diff --git a/src/tag.h b/src/tag.h index eddf8fa3a..47f425509 100644 --- a/src/tag.h +++ b/src/tag.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_tag_h__ #define INCLUDE_tag_h__ @@ -18,5 +24,6 @@ struct git_tag { void git_tag__free(git_tag *tag); int git_tag__parse(git_tag *tag, git_odb_object *obj); +int git_tag__parse_buffer(git_tag *tag, const char *data, size_t len); #endif diff --git a/src/thread-utils.c b/src/thread-utils.c index 5e8220f46..0ca01ef82 100644 --- a/src/thread-utils.c +++ b/src/thread-utils.c @@ -1,11 +1,17 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ #include "common.h" #include "thread-utils.h" #ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# include +# define WIN32_LEAN_AND_MEAN +# include #elif defined(hpux) || defined(__hpux) || defined(_hpux) -# include +# include #endif /* @@ -14,11 +20,11 @@ * with this disgusting nest of #ifdefs. */ #ifndef _SC_NPROCESSORS_ONLN -# ifdef _SC_NPROC_ONLN -# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN -# elif defined _SC_CRAY_NCPU -# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU -# endif +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif #endif int git_online_cpus(void) diff --git a/src/thread-utils.h b/src/thread-utils.h index 20d6022ea..a309e93d1 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -1,11 +1,17 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_thread_utils_h__ #define INCLUDE_thread_utils_h__ - +#include "common.h" /* Common operations even if threading has been disabled */ typedef struct { -#if defined(_MSC_VER) +#if defined(GIT_WIN32) volatile long val; #else volatile int val; @@ -27,10 +33,10 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) /* Pthreads Mutex */ #define git_mutex pthread_mutex_t -#define git_mutex_init(a) pthread_mutex_init(a, NULL) -#define git_mutex_lock(a) pthread_mutex_lock(a) +#define git_mutex_init(a) pthread_mutex_init(a, NULL) +#define git_mutex_lock(a) pthread_mutex_lock(a) #define git_mutex_unlock(a) pthread_mutex_unlock(a) -#define git_mutex_free(a) pthread_mutex_destroy(a) +#define git_mutex_free(a) pthread_mutex_destroy(a) /* Pthreads condition vars -- disabled by now */ #define git_cond unsigned int //pthread_cond_t @@ -42,10 +48,10 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) GIT_INLINE(int) git_atomic_inc(git_atomic *a) { -#ifdef __GNUC__ - return __sync_add_and_fetch(&a->val, 1); -#elif defined(_MSC_VER) +#if defined(GIT_WIN32) return InterlockedIncrement(&a->val); +#elif defined(__GNUC__) + return __sync_add_and_fetch(&a->val, 1); #else # error "Unsupported architecture for atomic operations" #endif @@ -53,10 +59,10 @@ GIT_INLINE(int) git_atomic_inc(git_atomic *a) GIT_INLINE(int) git_atomic_dec(git_atomic *a) { -#ifdef __GNUC__ - return __sync_sub_and_fetch(&a->val, 1); -#elif defined(_MSC_VER) +#if defined(GIT_WIN32) return InterlockedDecrement(&a->val); +#elif defined(__GNUC__) + return __sync_sub_and_fetch(&a->val, 1); #else # error "Unsupported architecture for atomic operations" #endif diff --git a/src/transport.c b/src/transport.c new file mode 100644 index 000000000..fb2b94946 --- /dev/null +++ b/src/transport.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/types.h" +#include "git2/remote.h" +#include "git2/net.h" +#include "transport.h" +#include "path.h" + +static struct { + char *prefix; + git_transport_cb fn; +} transports[] = { + {"git://", git_transport_git}, + {"http://", git_transport_http}, + {"https://", git_transport_https}, + {"file://", git_transport_local}, + {"git+ssh://", git_transport_dummy}, + {"ssh+git://", git_transport_dummy}, + {NULL, 0} +}; + +#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 + +static git_transport_cb transport_find_fn(const char *url) +{ + size_t i = 0; + + // First, check to see if it's an obvious URL, which a URL scheme + for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { + if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix))) + return transports[i].fn; + } + + /* still here? Check to see if the path points to a file on the local file system */ + if ((git_path_exists(url) == 0) && git_path_isdir(url)) + return &git_transport_local; + + /* It could be a SSH remote path. Check to see if there's a : */ + if (strrchr(url, ':')) + return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + + return NULL; +} + +/************** + * Public API * + **************/ + +int git_transport_dummy(git_transport **transport) +{ + GIT_UNUSED(transport); + giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); + return -1; +} + +int git_transport_new(git_transport **out, const char *url) +{ + git_transport_cb fn; + git_transport *transport; + int error; + + fn = transport_find_fn(url); + + if (fn == NULL) { + giterr_set(GITERR_NET, "Unsupported URL protocol"); + return -1; + } + + error = fn(&transport); + if (error < 0) + return error; + + transport->url = git__strdup(url); + GITERR_CHECK_ALLOC(transport->url); + + *out = transport; + + return 0; +} + +/* from remote.h */ +int git_remote_valid_url(const char *url) +{ + return transport_find_fn(url) != NULL; +} + +int git_remote_supported_url(const char* url) +{ + git_transport_cb transport_fn = transport_find_fn(url); + + return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy)); +} diff --git a/src/transport.h b/src/transport.h new file mode 100644 index 000000000..68b92f7a6 --- /dev/null +++ b/src/transport.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_transport_h__ +#define INCLUDE_transport_h__ + +#include "git2/net.h" +#include "git2/indexer.h" +#include "vector.h" +#include "posix.h" +#include "common.h" +#ifdef GIT_SSL +# include +# include +#endif + + +#define GIT_CAP_OFS_DELTA "ofs-delta" + +typedef struct git_transport_caps { + int common:1, + ofs_delta:1; +} git_transport_caps; + +#ifdef GIT_SSL +typedef struct gitno_ssl { + SSL_CTX *ctx; + SSL *ssl; +} gitno_ssl; +#endif + + +/* + * A day in the life of a network operation + * ======================================== + * + * The library gets told to ls-remote/push/fetch on/to/from some + * remote. We look at the URL of the remote and fill the function + * table with whatever is appropriate (the remote may be git over git, + * ssh or http(s). It may even be an hg or svn repository, the library + * at this level doesn't care, it just calls the helpers. + * + * The first call is to ->connect() which connects to the remote, + * making use of the direction if necessary. This function must also + * store the remote heads and any other information it needs. + * + * The next useful step is to call ->ls() to get the list of + * references available to the remote. These references may have been + * collected on connect, or we may build them now. For ls-remote, + * nothing else is needed other than closing the connection. + * Otherwise, the higher leves decide which objects we want to + * have. ->send_have() is used to tell the other end what we have. If + * we do need to download a pack, ->download_pack() is called. + * + * When we're done, we call ->close() to close the + * connection. ->free() takes care of freeing all the resources. + */ + +struct git_transport { + /** + * Where the repo lives + */ + char *url; + /** + * Whether we want to push or fetch + */ + int direction : 1, /* 0 fetch, 1 push */ + connected : 1, + check_cert: 1, + encrypt : 1; +#ifdef GIT_SSL + struct gitno_ssl ssl; +#endif + GIT_SOCKET socket; + /** + * Connect and store the remote heads + */ + int (*connect)(struct git_transport *transport, int dir); + /** + * Give a list of references, useful for ls-remote + */ + int (*ls)(struct git_transport *transport, git_headlist_cb list_cb, void *opaque); + /** + * Push the changes over + */ + int (*push)(struct git_transport *transport); + /** + * Negotiate the minimal amount of objects that need to be + * retrieved + */ + int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants); + /** + * Download the packfile + */ + int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); + /** + * Fetch the changes + */ + int (*fetch)(struct git_transport *transport); + /** + * Close the connection + */ + int (*close)(struct git_transport *transport); + /** + * Free the associated resources + */ + void (*free)(struct git_transport *transport); +}; + + +int git_transport_new(struct git_transport **transport, const char *url); +int git_transport_local(struct git_transport **transport); +int git_transport_git(struct git_transport **transport); +int git_transport_http(struct git_transport **transport); +int git_transport_https(struct git_transport **transport); +int git_transport_dummy(struct git_transport **transport); + +/** + Returns true if the passed URL is valid (a URL with a Git supported scheme, + or pointing to an existing path) +*/ +int git_transport_valid_url(const char *url); + +typedef struct git_transport git_transport; +typedef int (*git_transport_cb)(git_transport **transport); + +#endif diff --git a/src/transports/git.c b/src/transports/git.c new file mode 100644 index 000000000..45f571f20 --- /dev/null +++ b/src/transports/git.c @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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/net.h" +#include "git2/common.h" +#include "git2/types.h" +#include "git2/errors.h" +#include "git2/net.h" +#include "git2/revwalk.h" + +#include "vector.h" +#include "transport.h" +#include "pkt.h" +#include "common.h" +#include "netops.h" +#include "filebuf.h" +#include "repository.h" +#include "fetch.h" +#include "protocol.h" + +typedef struct { + git_transport parent; + git_protocol proto; + git_vector refs; + git_remote_head **heads; + git_transport_caps caps; + char buff[1024]; + gitno_buffer buf; +#ifdef GIT_WIN32 + WSADATA wsd; +#endif +} transport_git; + +/* + * Create a git procol request. + * + * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 + */ +static int gen_proto(git_buf *request, const char *cmd, const char *url) +{ + char *delim, *repo; + char default_command[] = "git-upload-pack"; + char host[] = "host="; + size_t len; + + delim = strchr(url, '/'); + if (delim == NULL) { + giterr_set(GITERR_NET, "Malformed URL"); + return -1; + } + + repo = delim; + + delim = strchr(url, ':'); + if (delim == NULL) + delim = strchr(url, '/'); + + if (cmd == NULL) + cmd = default_command; + + len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; + + git_buf_grow(request, len); + git_buf_printf(request, "%04x%s %s%c%s", + (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); + git_buf_put(request, url, delim - url); + git_buf_putc(request, '\0'); + + if (git_buf_oom(request)) + return -1; + + return 0; +} + +static int send_request(git_transport *t, const char *cmd, const char *url) +{ + int error; + git_buf request = GIT_BUF_INIT; + + error = gen_proto(&request, cmd, url); + if (error < 0) + goto cleanup; + + error = gitno_send(t, request.ptr, request.size, 0); + +cleanup: + git_buf_free(&request); + return error; +} + +/* + * Parse the URL and connect to a server, storing the socket in + * out. For convenience this also takes care of asking for the remote + * refs + */ +static int do_connect(transport_git *t, const char *url) +{ + char *host, *port; + const char prefix[] = "git://"; + + if (!git__prefixcmp(url, prefix)) + url += strlen(prefix); + + if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + return -1; + + if (gitno_connect((git_transport *)t, host, port) < 0) + goto on_error; + + if (send_request((git_transport *)t, NULL, url) < 0) + goto on_error; + + git__free(host); + git__free(port); + + return 0; + +on_error: + git__free(host); + git__free(port); + gitno_close(t->parent.socket); + return -1; +} + +/* + * Read from the socket and store the references in the vector + */ +static int store_refs(transport_git *t) +{ + gitno_buffer *buf = &t->buf; + int ret = 0; + + while (1) { + if ((ret = gitno_recv(buf)) < 0) + return -1; + if (ret == 0) /* Orderly shutdown, so exit */ + return 0; + + ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset); + if (ret == GIT_EBUFS) { + gitno_consume_n(buf, buf->len); + continue; + } + + if (ret < 0) + return ret; + + gitno_consume_n(buf, buf->offset); + + if (t->proto.flush) { /* No more refs */ + t->proto.flush = 0; + return 0; + } + } +} + +static int detect_caps(transport_git *t) +{ + git_vector *refs = &t->refs; + git_pkt_ref *pkt; + git_transport_caps *caps = &t->caps; + const char *ptr; + + pkt = git_vector_get(refs, 0); + /* No refs or capabilites, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return 0; + + ptr = pkt->capabilities; + while (ptr != NULL && *ptr != '\0') { + if (*ptr == ' ') + ptr++; + + if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + caps->common = caps->ofs_delta = 1; + ptr += strlen(GIT_CAP_OFS_DELTA); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +/* + * Since this is a network connection, we need to parse and store the + * pkt-lines at this stage and keep them there. + */ +static int git_connect(git_transport *transport, int direction) +{ + transport_git *t = (transport_git *) transport; + + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over git:// is not supported"); + return -1; + } + + t->parent.direction = direction; + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; + + /* Connect and ask for the refs */ + if (do_connect(t, transport->url) < 0) + goto cleanup; + + gitno_buffer_setup(transport, &t->buf, t->buff, sizeof(t->buff)); + + t->parent.connected = 1; + if (store_refs(t) < 0) + goto cleanup; + + if (detect_caps(t) < 0) + goto cleanup; + + return 0; +cleanup: + git_vector_free(&t->refs); + return -1; +} + +static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_pkt *p = NULL; + + git_vector_foreach(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, opaque) < 0) { + giterr_set(GITERR_NET, "User callback returned error"); + return -1; + } + } + + return 0; +} + +/* Wait until we get an ack from the */ +static int recv_pkt(gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end; + git_pkt *pkt; + int pkt_type, error; + + do { + /* Wait for max. 1 second */ + if ((error = gitno_select_in(buf, 1, 0)) < 0) { + return -1; + } else if (error == 0) { + /* + * Some servers don't respond immediately, so if this + * happens, we keep sending information until it + * answers. Pretend we received a NAK to convince higher + * layers to do so. + */ + return GIT_PKT_NAK; + } + + if ((error = gitno_recv(buf)) < 0) + return -1; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + if (error == GIT_EBUFS) + continue; + if (error < 0) + return -1; + } while (error); + + gitno_consume(buf, line_end); + pkt_type = pkt->type; + git__free(pkt); + + return pkt_type; +} + +static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +{ + transport_git *t = (transport_git *) transport; + git_revwalk *walk; + git_oid oid; + int error; + unsigned int i; + git_buf data = GIT_BUF_INIT; + gitno_buffer *buf = &t->buf; + + if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0) + return -1; + + if (git_fetch_setup_walk(&walk, repo) < 0) + goto on_error; + + if (gitno_send(transport, data.ptr, data.size, 0) < 0) + goto on_error; + + git_buf_clear(&data); + /* + * 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. + */ + i = 0; + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + int pkt_type; + + git_pkt_buffer_flush(&data); + if (git_buf_oom(&data)) + goto on_error; + + if (gitno_send(transport, data.ptr, data.size, 0) < 0) + goto on_error; + + pkt_type = recv_pkt(buf); + + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + giterr_set(GITERR_NET, "Unexpected pkt type"); + goto on_error; + } + + } + } + if (error < 0 && error != GIT_REVWALKOVER) + goto on_error; + + /* Tell the other end that we're done negotiating */ + git_buf_clear(&data); + git_pkt_buffer_flush(&data); + git_pkt_buffer_done(&data); + if (gitno_send(transport, data.ptr, data.size, 0) < 0) + goto on_error; + + git_buf_free(&data); + git_revwalk_free(walk); + return 0; + +on_error: + git_buf_free(&data); + git_revwalk_free(walk); + return -1; +} + +static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) +{ + transport_git *t = (transport_git *) transport; + int error = 0, read_bytes; + gitno_buffer *buf = &t->buf; + git_pkt *pkt; + const char *line_end, *ptr; + + /* + * For now, we ignore everything and wait for the pack + */ + do { + ptr = buf->data; + /* Whilst we're searching for the pack */ + while (1) { + if (buf->offset == 0) { + break; + } + + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + if (error == GIT_EBUFS) + break; + + if (error < 0) + return error; + + if (pkt->type == GIT_PKT_PACK) { + git__free(pkt); + return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats); + } + + /* For now we don't care about anything */ + git__free(pkt); + gitno_consume(buf, line_end); + } + + read_bytes = gitno_recv(buf); + } while (read_bytes); + + return read_bytes; +} + +static int git_close(git_transport *t) +{ + git_buf buf = GIT_BUF_INIT; + + if (git_pkt_buffer_flush(&buf) < 0) + return -1; + /* Can't do anything if there's an error, so don't bother checking */ + gitno_send(t, buf.ptr, buf.size, 0); + + if (gitno_close(t->socket) < 0) { + giterr_set(GITERR_NET, "Failed to close socket"); + return -1; + } + + t->connected = 0; + +#ifdef GIT_WIN32 + WSACleanup(); +#endif + + return 0; +} + +static void git_free(git_transport *transport) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + unsigned int i; + + for (i = 0; i < refs->length; ++i) { + git_pkt *p = git_vector_get(refs, i); + git_pkt_free(p); + } + + git_vector_free(refs); + git__free(t->heads); + git_buf_free(&t->proto.buf); + git__free(t->parent.url); + git__free(t); +} + +int git_transport_git(git_transport **out) +{ + transport_git *t; +#ifdef GIT_WIN32 + int ret; +#endif + + t = git__malloc(sizeof(transport_git)); + GITERR_CHECK_ALLOC(t); + + memset(t, 0x0, sizeof(transport_git)); + + t->parent.connect = git_connect; + t->parent.ls = git_ls; + t->parent.negotiate_fetch = git_negotiate_fetch; + t->parent.download_pack = git_download_pack; + t->parent.close = git_close; + t->parent.free = git_free; + t->proto.refs = &t->refs; + t->proto.transport = (git_transport *) t; + + *out = (git_transport *) t; + +#ifdef GIT_WIN32 + ret = WSAStartup(MAKEWORD(2,2), &t->wsd); + if (ret != 0) { + git_free(*out); + giterr_set(GITERR_NET, "Winsock init failed"); + return -1; + } +#endif + + return 0; +} diff --git a/src/transports/http.c b/src/transports/http.c new file mode 100644 index 000000000..4139a2fa6 --- /dev/null +++ b/src/transports/http.c @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include "git2.h" +#include "http_parser.h" + +#include "transport.h" +#include "common.h" +#include "netops.h" +#include "buffer.h" +#include "pkt.h" +#include "refs.h" +#include "pack.h" +#include "fetch.h" +#include "filebuf.h" +#include "repository.h" +#include "protocol.h" + +enum last_cb { + NONE, + FIELD, + VALUE +}; + +typedef struct { + git_transport parent; + git_protocol proto; + git_vector refs; + git_vector common; + git_buf buf; + git_remote_head **heads; + int error; + int transfer_finished :1, + ct_found :1, + ct_finished :1, + pack_ready :1; + enum last_cb last_cb; + http_parser parser; + char *content_type; + char *path; + char *host; + char *port; + char *service; + git_transport_caps caps; +#ifdef GIT_WIN32 + WSADATA wsd; +#endif +} transport_http; + +static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, + const char *service, ssize_t content_length, int ls) +{ + if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ + path = "/"; + + if (ls) { + git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service); + } else { + git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service); + } + git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); + git_buf_printf(buf, "Host: %s\r\n", host); + if (content_length > 0) { + git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service); + git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service); + git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length); + } else { + git_buf_puts(buf, "Accept: */*\r\n"); + } + git_buf_puts(buf, "\r\n"); + + if (git_buf_oom(buf)) + return -1; + + return 0; +} + +static int do_connect(transport_http *t, const char *host, const char *port) +{ + if (t->parent.connected && http_should_keep_alive(&t->parser)) + return 0; + + if (gitno_connect((git_transport *) t, host, port) < 0) + return -1; + + t->parent.connected = 1; + + return 0; +} + +/* + * The HTTP parser is streaming, so we need to wait until we're in the + * field handler before we can be sure that we can store the previous + * value. Right now, we only care about the + * Content-Type. on_header_{field,value} should be kept generic enough + * to work for any request. + */ + +static const char *typestr = "Content-Type"; + +static int on_header_field(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + + if (t->last_cb == VALUE && t->ct_found) { + t->ct_finished = 1; + t->ct_found = 0; + t->content_type = git__strdup(git_buf_cstr(buf)); + GITERR_CHECK_ALLOC(t->content_type); + git_buf_clear(buf); + } + + if (t->ct_found) { + t->last_cb = FIELD; + return 0; + } + + if (t->last_cb != FIELD) + git_buf_clear(buf); + + git_buf_put(buf, str, len); + t->last_cb = FIELD; + + return git_buf_oom(buf); +} + +static int on_header_value(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + + if (t->ct_finished) { + t->last_cb = VALUE; + return 0; + } + + if (t->last_cb == VALUE) + git_buf_put(buf, str, len); + + if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) { + t->ct_found = 1; + git_buf_clear(buf); + git_buf_put(buf, str, len); + } + + t->last_cb = VALUE; + + return git_buf_oom(buf); +} + +static int on_headers_complete(http_parser *parser) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + + /* The content-type is text/plain for 404, so don't validate */ + if (parser->status_code == 404) { + git_buf_clear(buf); + return 0; + } + + if (t->content_type == NULL) { + t->content_type = git__strdup(git_buf_cstr(buf)); + if (t->content_type == NULL) + return t->error = -1; + } + + git_buf_clear(buf); + git_buf_printf(buf, "application/x-git-%s-advertisement", t->service); + if (git_buf_oom(buf)) + return t->error = -1; + + if (strcmp(t->content_type, git_buf_cstr(buf))) + return t->error = -1; + + git_buf_clear(buf); + return 0; +} + +static int on_body_store_refs(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + + if (parser->status_code == 404) { + return git_buf_put(&t->buf, str, len); + } + + return git_protocol_store_refs(&t->proto, str, len); +} + +static int on_message_complete(http_parser *parser) +{ + transport_http *t = (transport_http *) parser->data; + + t->transfer_finished = 1; + + if (parser->status_code == 404) { + giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf)); + t->error = -1; + } + + return 0; +} + +static int store_refs(transport_http *t) +{ + http_parser_settings settings; + char buffer[1024]; + gitno_buffer buf; + git_pkt *pkt; + int ret; + + http_parser_init(&t->parser, HTTP_RESPONSE); + t->parser.data = t; + memset(&settings, 0x0, sizeof(http_parser_settings)); + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_headers_complete = on_headers_complete; + settings.on_body = on_body_store_refs; + settings.on_message_complete = on_message_complete; + + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); + + while(1) { + size_t parsed; + + if ((ret = gitno_recv(&buf)) < 0) + return -1; + + parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); + /* Both should happen at the same time */ + if (parsed != buf.offset || t->error < 0) + return t->error; + + gitno_consume_n(&buf, parsed); + + if (ret == 0 || t->transfer_finished) + return 0; + } + + pkt = git_vector_get(&t->refs, 0); + if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { + giterr_set(GITERR_NET, "Invalid HTTP response"); + return t->error = -1; + } else { + git_vector_remove(&t->refs, 0); + } + + return 0; +} + +static int http_connect(git_transport *transport, int direction) +{ + transport_http *t = (transport_http *) transport; + int ret; + git_buf request = GIT_BUF_INIT; + const char *service = "upload-pack"; + const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; + const char *default_port; + + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); + return -1; + } + + t->parent.direction = direction; + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; + + if (!git__prefixcmp(url, prefix_http)) { + url = t->parent.url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + } + + t->path = strchr(url, '/'); + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + goto cleanup; + + t->service = git__strdup(service); + GITERR_CHECK_ALLOC(t->service); + + if ((ret = do_connect(t, t->host, t->port)) < 0) + goto cleanup; + + /* Generate and send the HTTP request */ + if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + goto cleanup; + } + + + if (gitno_send(transport, request.ptr, request.size, 0) < 0) + goto cleanup; + + ret = store_refs(t); + +cleanup: + git_buf_free(&request); + git_buf_clear(&t->buf); + + return ret; +} + +static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) +{ + transport_http *t = (transport_http *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_pkt_ref *p; + + git_vector_foreach(refs, i, p) { + if (p->type != GIT_PKT_REF) + continue; + + if (list_cb(&p->head, opaque) < 0) { + giterr_set(GITERR_NET, "The user callback returned error"); + return -1; + } + } + + return 0; +} + +static int on_body_parse_response(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + git_vector *common = &t->common; + int error; + const char *line_end, *ptr; + + if (len == 0) { /* EOF */ + if (git_buf_len(buf) != 0) { + giterr_set(GITERR_NET, "Unexpected EOF"); + return t->error = -1; + } else { + return 0; + } + } + + git_buf_put(buf, str, len); + ptr = buf->ptr; + while (1) { + git_pkt *pkt; + + if (git_buf_len(buf) == 0) + return 0; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); + if (error == GIT_EBUFS) { + return 0; /* Ask for more */ + } + if (error < 0) + return t->error = -1; + + git_buf_consume(buf, line_end); + + if (pkt->type == GIT_PKT_PACK) { + git__free(pkt); + t->pack_ready = 1; + return 0; + } + + if (pkt->type == GIT_PKT_NAK) { + git__free(pkt); + return 0; + } + + if (pkt->type != GIT_PKT_ACK) { + git__free(pkt); + continue; + } + + if (git_vector_insert(common, pkt) < 0) + return -1; + } + + return error; + +} + +static int parse_response(transport_http *t) +{ + int ret = 0; + http_parser_settings settings; + char buffer[1024]; + gitno_buffer buf; + + http_parser_init(&t->parser, HTTP_RESPONSE); + t->parser.data = t; + t->transfer_finished = 0; + memset(&settings, 0x0, sizeof(http_parser_settings)); + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_headers_complete = on_headers_complete; + settings.on_body = on_body_parse_response; + settings.on_message_complete = on_message_complete; + + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); + + while(1) { + size_t parsed; + + if ((ret = gitno_recv(&buf)) < 0) + return -1; + + parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); + /* Both should happen at the same time */ + if (parsed != buf.offset || t->error < 0) + return t->error; + + gitno_consume_n(&buf, parsed); + + if (ret == 0 || t->transfer_finished || t->pack_ready) { + return 0; + } + } + + return ret; +} + +static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +{ + transport_http *t = (transport_http *) transport; + int ret; + unsigned int i; + char buff[128]; + gitno_buffer buf; + git_revwalk *walk = NULL; + git_oid oid; + git_pkt_ack *pkt; + git_vector *common = &t->common; + git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT; + + gitno_buffer_setup(transport, &buf, buff, sizeof(buff)); + + if (git_vector_init(common, 16, NULL) < 0) + return -1; + + if (git_fetch_setup_walk(&walk, repo) < 0) + return -1; + + do { + if ((ret = do_connect(t, t->host, t->port)) < 0) + goto cleanup; + + if ((ret = git_pkt_buffer_wants(wants, &t->caps, &data)) < 0) + goto cleanup; + + /* We need to send these on each connection */ + git_vector_foreach (common, i, pkt) { + if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto cleanup; + } + + i = 0; + while ((i < 20) && ((ret = git_revwalk_next(&oid, walk)) == 0)) { + if ((ret = git_pkt_buffer_have(&oid, &data)) < 0) + goto cleanup; + + i++; + } + + git_pkt_buffer_done(&data); + + if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", data.size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, data.ptr, data.size, 0)) < 0) + goto cleanup; + + git_buf_clear(&request); + git_buf_clear(&data); + + if (ret < 0 || i >= 256) + break; + + if ((ret = parse_response(t)) < 0) + goto cleanup; + + if (t->pack_ready) { + ret = 0; + goto cleanup; + } + + } while(1); + +cleanup: + git_buf_free(&request); + git_buf_free(&data); + git_revwalk_free(walk); + return ret; +} + +typedef struct { + git_indexer_stream *idx; + git_indexer_stats *stats; + transport_http *transport; +} download_pack_cbdata; + +static int on_message_complete_download_pack(http_parser *parser) +{ + download_pack_cbdata *data = (download_pack_cbdata *) parser->data; + + data->transport->transfer_finished = 1; + + return 0; +} +static int on_body_download_pack(http_parser *parser, const char *str, size_t len) +{ + download_pack_cbdata *data = (download_pack_cbdata *) parser->data; + transport_http *t = data->transport; + git_indexer_stream *idx = data->idx; + git_indexer_stats *stats = data->stats; + + return t->error = git_indexer_stream_add(idx, str, len, stats); +} + +/* + * As the server is probably using Transfer-Encoding: chunked, we have + * to use the HTTP parser to download the pack instead of giving it to + * the simple downloader. Furthermore, we're using keep-alive + * connections, so the simple downloader would just hang. + */ +static int http_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) +{ + transport_http *t = (transport_http *) transport; + git_buf *oldbuf = &t->buf; + int recvd; + http_parser_settings settings; + char buffer[1024]; + gitno_buffer buf; + git_indexer_stream *idx = NULL; + download_pack_cbdata data; + + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + + if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { + giterr_set(GITERR_NET, "The pack doesn't start with a pack signature"); + return -1; + } + + if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) + return -1; + + /* + * This is part of the previous response, so we don't want to + * re-init the parser, just set these two callbacks. + */ + memset(stats, 0, sizeof(git_indexer_stats)); + data.stats = stats; + data.idx = idx; + data.transport = t; + t->parser.data = &data; + t->transfer_finished = 0; + memset(&settings, 0x0, sizeof(settings)); + settings.on_message_complete = on_message_complete_download_pack; + settings.on_body = on_body_download_pack; + *bytes = git_buf_len(oldbuf); + + if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) + goto on_error; + + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + + do { + size_t parsed; + + if ((recvd = gitno_recv(&buf)) < 0) + goto on_error; + + parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); + if (parsed != buf.offset || t->error < 0) + goto on_error; + + *bytes += recvd; + gitno_consume_n(&buf, parsed); + } while (recvd > 0 && !t->transfer_finished); + + if (git_indexer_stream_finalize(idx, stats) < 0) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + +on_error: + git_indexer_stream_free(idx); + return -1; +} + +static int http_close(git_transport *transport) +{ + if (gitno_ssl_teardown(transport) < 0) + return -1; + + if (gitno_close(transport->socket) < 0) { + giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); + return -1; + } + + transport->connected = 0; + + return 0; +} + + +static void http_free(git_transport *transport) +{ + transport_http *t = (transport_http *) transport; + git_vector *refs = &t->refs; + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + +#ifdef GIT_WIN32 + /* cleanup the WSA context. note that this context + * can be initialized more than once with WSAStartup(), + * and needs to be cleaned one time for each init call + */ + WSACleanup(); +#endif + + git_vector_foreach(refs, i, p) { + git_pkt_free(p); + } + git_vector_free(refs); + git_vector_foreach(common, i, p) { + git_pkt_free(p); + } + git_vector_free(common); + git_buf_free(&t->buf); + git_buf_free(&t->proto.buf); + git__free(t->heads); + git__free(t->content_type); + git__free(t->host); + git__free(t->port); + git__free(t->service); + git__free(t->parent.url); + git__free(t); +} + +int git_transport_http(git_transport **out) +{ + transport_http *t; + + t = git__malloc(sizeof(transport_http)); + GITERR_CHECK_ALLOC(t); + + memset(t, 0x0, sizeof(transport_http)); + + t->parent.connect = http_connect; + t->parent.ls = http_ls; + t->parent.negotiate_fetch = http_negotiate_fetch; + t->parent.download_pack = http_download_pack; + t->parent.close = http_close; + t->parent.free = http_free; + t->proto.refs = &t->refs; + t->proto.transport = (git_transport *) t; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) { + http_free((git_transport *) t); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } +#endif + + *out = (git_transport *) t; + return 0; +} + +int git_transport_https(git_transport **out) +{ +#ifdef GIT_SSL + transport_http *t; + if (git_transport_http((git_transport **)&t) < 0) + return -1; + + t->parent.encrypt = 1; + t->parent.check_cert = 1; + *out = (git_transport *) t; + + return 0; +#else + GIT_UNUSED(out); + + giterr_set(GITERR_NET, "HTTPS support not available"); + return -1; +#endif +} diff --git a/src/transports/local.c b/src/transports/local.c new file mode 100644 index 000000000..0e1ae3752 --- /dev/null +++ b/src/transports/local.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "git2/types.h" +#include "git2/net.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "refs.h" +#include "transport.h" +#include "posix.h" +#include "path.h" +#include "buffer.h" + +typedef struct { + git_transport parent; + git_repository *repo; + git_vector refs; +} transport_local; + +static int add_ref(transport_local *t, const char *name) +{ + const char peeled[] = "^{}"; + git_remote_head *head; + git_object *obj = NULL, *target = NULL; + git_buf buf = GIT_BUF_INIT; + + head = git__malloc(sizeof(git_remote_head)); + GITERR_CHECK_ALLOC(head); + + head->name = git__strdup(name); + GITERR_CHECK_ALLOC(head->name); + + if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0 || + git_vector_insert(&t->refs, head) < 0) + { + git__free(head->name); + git__free(head); + return -1; + } + + /* If it's not a tag, we don't need to try to peel it */ + if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + return 0; + + if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0) + return -1; + + head = NULL; + + /* If it's not an annotated tag, just get out */ + if (git_object_type(obj) != GIT_OBJ_TAG) { + git_object_free(obj); + return 0; + } + + /* And if it's a tag, peel it, and add it to the list */ + head = git__malloc(sizeof(git_remote_head)); + GITERR_CHECK_ALLOC(head); + if (git_buf_join(&buf, 0, name, peeled) < 0) + return -1; + + head->name = git_buf_detach(&buf); + + if (git_tag_peel(&target, (git_tag *) obj) < 0) + goto on_error; + + git_oid_cpy(&head->oid, git_object_id(target)); + git_object_free(obj); + git_object_free(target); + + if (git_vector_insert(&t->refs, head) < 0) + return -1; + + return 0; + +on_error: + git_object_free(obj); + git_object_free(target); + return -1; +} + +static int store_refs(transport_local *t) +{ + unsigned int i; + git_strarray ref_names = {0}; + + assert(t); + + if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 || + git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0) + goto on_error; + + /* Sort the references first */ + git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); + + /* Add HEAD */ + if (add_ref(t, GIT_HEAD_FILE) < 0) + goto on_error; + + for (i = 0; i < ref_names.count; ++i) { + if (add_ref(t, ref_names.strings[i]) < 0) + goto on_error; + } + + git_strarray_free(&ref_names); + return 0; + +on_error: + git_vector_free(&t->refs); + git_strarray_free(&ref_names); + return -1; +} + +static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +{ + transport_local *t = (transport_local *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_remote_head *h; + + assert(transport && transport->connected); + + git_vector_foreach(refs, i, h) { + if (list_cb(h, payload) < 0) + return -1; + } + + return 0; +} + +/* + * Try to open the url as a git directory. The direction doesn't + * matter in this case because we're calulating the heads ourselves. + */ +static int local_connect(git_transport *transport, int direction) +{ + git_repository *repo; + int error; + transport_local *t = (transport_local *) transport; + const char *path; + git_buf buf = GIT_BUF_INIT; + + GIT_UNUSED(direction); + + /* The repo layer doesn't want the prefix */ + if (!git__prefixcmp(transport->url, "file://")) { + if (git_path_fromurl(&buf, transport->url) < 0) { + git_buf_free(&buf); + return -1; + } + path = git_buf_cstr(&buf); + + } else { /* We assume transport->url is already a path */ + path = transport->url; + } + + error = git_repository_open(&repo, path); + + git_buf_free(&buf); + + if (error < 0) + return -1; + + t->repo = repo; + + if (store_refs(t) < 0) + return -1; + + t->parent.connected = 1; + + return 0; +} + +static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +{ + GIT_UNUSED(transport); + GIT_UNUSED(repo); + GIT_UNUSED(wants); + + giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry"); + return -1; +} + +static int local_close(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + t->parent.connected = 0; + git_repository_free(t->repo); + t->repo = NULL; + + return 0; +} + +static void local_free(git_transport *transport) +{ + unsigned int i; + transport_local *t = (transport_local *) transport; + git_vector *vec = &t->refs; + git_remote_head *h; + + assert(transport); + + git_vector_foreach (vec, i, h) { + git__free(h->name); + git__free(h); + } + git_vector_free(vec); + + git__free(t->parent.url); + git__free(t); +} + +/************** + * Public API * + **************/ + +int git_transport_local(git_transport **out) +{ + transport_local *t; + + t = git__malloc(sizeof(transport_local)); + GITERR_CHECK_ALLOC(t); + + memset(t, 0x0, sizeof(transport_local)); + + t->parent.connect = local_connect; + t->parent.ls = local_ls; + t->parent.negotiate_fetch = local_negotiate_fetch; + t->parent.close = local_close; + t->parent.free = local_free; + + *out = (git_transport *) t; + + return 0; +} diff --git a/src/tree-cache.c b/src/tree-cache.c new file mode 100644 index 000000000..ebc2c6807 --- /dev/null +++ b/src/tree-cache.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "tree-cache.h" + +static git_tree_cache *find_child(const git_tree_cache *tree, const char *path) +{ + size_t i, dirlen; + const char *end; + + end = strchr(path, '/'); + if (end == NULL) { + end = strrchr(path, '\0'); + } + + dirlen = end - path; + + for (i = 0; i < tree->children_count; ++i) { + const char *childname = tree->children[i]->name; + + if (strlen(childname) == dirlen && !memcmp(path, childname, dirlen)) + return tree->children[i]; + } + + return NULL; +} + +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) + return; + + tree->entries = -1; + + while (ptr != NULL) { + end = strchr(ptr, '/'); + + if (end == NULL) /* End of path */ + break; + + tree = find_child(tree, ptr); + if (tree == NULL) /* We don't have that tree */ + return; + + tree->entries = -1; + ptr = end + 1; + } +} + +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) { + return NULL; + } + + while (1) { + end = strchr(ptr, '/'); + + tree = find_child(tree, ptr); + if (tree == NULL) { /* Can't find it */ + return NULL; + } + + if (end == NULL || end + 1 == '\0') + return tree; + + ptr = end + 1; + } +} + +static int read_tree_internal(git_tree_cache **out, + const char **buffer_in, const char *buffer_end, git_tree_cache *parent) +{ + git_tree_cache *tree = NULL; + const char *name_start, *buffer; + int count; + size_t name_len; + + buffer = name_start = *buffer_in; + + if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) + goto corrupted; + + if (++buffer >= buffer_end) + goto corrupted; + + name_len = strlen(name_start); + tree = git__malloc(sizeof(git_tree_cache) + name_len + 1); + GITERR_CHECK_ALLOC(tree); + + memset(tree, 0x0, sizeof(git_tree_cache)); + tree->parent = parent; + + /* NUL-terminated tree name */ + memcpy(tree->name, name_start, name_len); + tree->name[name_len] = '\0'; + + /* Blank-terminated ASCII decimal number of entries in this tree */ + if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < -1) + goto corrupted; + + tree->entries = count; + + if (*buffer != ' ' || ++buffer >= buffer_end) + goto corrupted; + + /* Number of children of the tree, newline-terminated */ + if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0) + goto corrupted; + + tree->children_count = count; + + if (*buffer != '\n' || ++buffer > buffer_end) + goto corrupted; + + /* The SHA1 is only there if it's not invalidated */ + if (tree->entries >= 0) { + /* 160-bit SHA-1 for this tree and it's children */ + if (buffer + GIT_OID_RAWSZ > buffer_end) + goto corrupted; + + git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); + buffer += GIT_OID_RAWSZ; + } + + /* Parse children: */ + if (tree->children_count > 0) { + unsigned int i; + + tree->children = git__malloc(tree->children_count * sizeof(git_tree_cache *)); + GITERR_CHECK_ALLOC(tree->children); + + for (i = 0; i < tree->children_count; ++i) { + if (read_tree_internal(&tree->children[i], &buffer, buffer_end, tree) < 0) + return -1; + } + } + + *buffer_in = buffer; + *out = tree; + return 0; + + corrupted: + git_tree_cache_free(tree); + giterr_set(GITERR_INDEX, "Corruped TREE extension in index"); + return -1; +} + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size) +{ + const char *buffer_end = buffer + buffer_size; + + if (read_tree_internal(tree, &buffer, buffer_end, NULL) < 0) + return -1; + + if (buffer < buffer_end) { + giterr_set(GITERR_INDEX, "Corruped TREE extension in index (unexpected trailing data)"); + return -1; + } + + return 0; +} + +void git_tree_cache_free(git_tree_cache *tree) +{ + unsigned int i; + + if (tree == NULL) + return; + + for (i = 0; i < tree->children_count; ++i) + git_tree_cache_free(tree->children[i]); + + git__free(tree->children); + git__free(tree); +} diff --git a/src/tree-cache.h b/src/tree-cache.h new file mode 100644 index 000000000..41fde997a --- /dev/null +++ b/src/tree-cache.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_tree_cache_h__ +#define INCLUDE_tree_cache_h__ + +#include "common.h" +#include "git2/oid.h" + +struct git_tree_cache { + struct git_tree_cache *parent; + struct git_tree_cache **children; + size_t children_count; + + ssize_t entries; + git_oid oid; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct git_tree_cache git_tree_cache; + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size); +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); +void git_tree_cache_free(git_tree_cache *tree); + +#endif diff --git a/src/tree.c b/src/tree.c index 60413e276..92b1b1e39 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -33,33 +15,109 @@ #define MAX_FILEMODE 0777777 #define MAX_FILEMODE_BYTES 6 -static int valid_attributes(const int attributes) { - return attributes >= 0 && attributes <= MAX_FILEMODE; -} - -int entry_search_cmp(const void *key, const void *array_member) +static int valid_attributes(const int attributes) { - const char *filename = (const char *)key; - const git_tree_entry *entry = *(const git_tree_entry **)(array_member); - - return strcmp(filename, entry->filename); + return attributes >= 0 && attributes <= MAX_FILEMODE; } -#if 0 -static int valid_attributes(const int attributes) { - return attributes >= 0 && attributes <= MAX_FILEMODE; -} -#endif - -int entry_sort_cmp(const void *a, const void *b) +static int valid_entry_name(const char *filename) { - const git_tree_entry *entry_a = *(const git_tree_entry **)(a); - const git_tree_entry *entry_b = *(const git_tree_entry **)(b); + return strlen(filename) > 0 && strchr(filename, '/') == NULL; +} - return gitfo_cmp_path(entry_a->filename, strlen(entry_a->filename), - entry_a->attr & 040000, - entry_b->filename, strlen(entry_b->filename), - entry_b->attr & 040000); +static int entry_sort_cmp(const void *a, const void *b) +{ + const git_tree_entry *entry_a = (const git_tree_entry *)(a); + const git_tree_entry *entry_b = (const git_tree_entry *)(b); + + return git_path_cmp( + entry_a->filename, entry_a->filename_len, git_tree_entry__is_tree(entry_a), + entry_b->filename, entry_b->filename_len, git_tree_entry__is_tree(entry_b)); +} + + +struct tree_key_search { + const char *filename; + size_t filename_len; +}; + +static int homing_search_cmp(const void *key, const void *array_member) +{ + const struct tree_key_search *ksearch = key; + const git_tree_entry *entry = array_member; + + const size_t len1 = ksearch->filename_len; + const size_t len2 = entry->filename_len; + + return memcmp( + ksearch->filename, + entry->filename, + len1 < len2 ? len1 : len2 + ); +} + +/* + * Search for an entry in a given tree. + * + * Note that this search is performed in two steps because + * of the way tree entries are sorted internally in git: + * + * Entries in a tree are not sorted alphabetically; two entries + * with the same root prefix will have different positions + * depending on whether they are folders (subtrees) or normal files. + * + * Consequently, it is not possible to find an entry on the tree + * with a binary search if you don't know whether the filename + * you're looking for is a folder or a normal file. + * + * To work around this, we first perform a homing binary search + * on the tree, using the minimal length root prefix of our filename. + * Once the comparisons for this homing search start becoming + * ambiguous because of folder vs file sorting, we look linearly + * around the area for our target file. + */ +static int tree_key_search(git_vector *entries, const char *filename) +{ + struct tree_key_search ksearch; + const git_tree_entry *entry; + + int homing, i; + + ksearch.filename = filename; + ksearch.filename_len = strlen(filename); + + /* Initial homing search; find an entry on the tree with + * the same prefix as the filename we're looking for */ + homing = git_vector_bsearch2(entries, &homing_search_cmp, &ksearch); + if (homing < 0) + return homing; + + /* We found a common prefix. Look forward as long as + * there are entries that share the common prefix */ + for (i = homing; i < (int)entries->length; ++i) { + entry = entries->contents[i]; + + if (homing_search_cmp(&ksearch, entry) < 0) + break; + + if (strcmp(filename, entry->filename) == 0) + return i; + } + + /* If we haven't found our filename yet, look backwards + * too as long as we have entries with the same prefix */ + for (i = homing - 1; i >= 0; --i) { + entry = entries->contents[i]; + + if (homing_search_cmp(&ksearch, entry) > 0) + break; + + if (strcmp(filename, entry->filename) == 0) + return i; + } + + /* The filename doesn't exist at all */ + return GIT_ENOTFOUND; } void git_tree__free(git_tree *tree) @@ -70,12 +128,12 @@ void git_tree__free(git_tree *tree) git_tree_entry *e; e = git_vector_get(&tree->entries, i); - free(e->filename); - free(e); + git__free(e->filename); + git__free(e); } git_vector_free(&tree->entries); - free(tree); + git__free(tree); } const git_oid *git_tree_id(git_tree *c) @@ -100,7 +158,22 @@ const git_oid *git_tree_entry_id(const git_tree_entry *entry) return &entry->oid; } -int git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry) +git_otype git_tree_entry_type(const git_tree_entry *entry) +{ + assert(entry); + + if (S_ISGITLINK(entry->attr)) + return GIT_OBJ_COMMIT; + else if (S_ISDIR(entry->attr)) + return GIT_OBJ_TREE; + else + return GIT_OBJ_BLOB; +} + +int git_tree_entry_to_object( + git_object **object_out, + git_repository *repo, + const git_tree_entry *entry) { assert(entry && object_out); return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY); @@ -112,56 +185,84 @@ const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename assert(tree && filename); - idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, filename); + idx = tree_key_search(&tree->entries, filename); if (idx == GIT_ENOTFOUND) return NULL; return git_vector_get(&tree->entries, idx); } -const git_tree_entry *git_tree_entry_byindex(git_tree *tree, int idx) +const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx) { assert(tree); - return git_vector_get(&tree->entries, (unsigned int)idx); + return git_vector_get(&tree->entries, idx); } -size_t git_tree_entrycount(git_tree *tree) +int git_tree__prefix_position(git_tree *tree, const char *path) +{ + git_vector *entries = &tree->entries; + struct tree_key_search ksearch; + unsigned int at_pos; + + ksearch.filename = path; + ksearch.filename_len = strlen(path); + + /* Find tree entry with appropriate prefix */ + git_vector_bsearch3(&at_pos, entries, &homing_search_cmp, &ksearch); + + for (; at_pos < entries->length; ++at_pos) { + const git_tree_entry *entry = entries->contents[at_pos]; + if (homing_search_cmp(&ksearch, entry) < 0) + break; + } + + for (; at_pos > 0; --at_pos) { + const git_tree_entry *entry = entries->contents[at_pos - 1]; + if (homing_search_cmp(&ksearch, entry) > 0) + break; + } + + return at_pos; +} + +unsigned int git_tree_entrycount(git_tree *tree) { assert(tree); return tree->entries.length; } +static int tree_error(const char *str) +{ + giterr_set(GITERR_TREE, "%s", str); + return -1; +} + static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end) { - int error = GIT_SUCCESS; - - if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0) + return -1; while (buffer < buffer_end) { git_tree_entry *entry; + int tmp; entry = git__calloc(1, sizeof(git_tree_entry)); - if (entry == NULL) { - error = GIT_ENOMEM; - break; - } + GITERR_CHECK_ALLOC(entry); - if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&tree->entries, entry) < 0) + return -1; - if (git__strtol32((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes"); + if (git__strtol32(&tmp, buffer, &buffer, 8) < 0 || + !buffer || !valid_attributes(tmp)) + return tree_error("Failed to parse tree. Can't parse attributes"); - if (*buffer++ != ' ') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); - break; - } + entry->attr = tmp; - if (memchr(buffer, 0, buffer_end - buffer) == NULL) { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); - break; - } + if (*buffer++ != ' ') + return tree_error("Failed to parse tree. Object is corrupted"); + + if (memchr(buffer, 0, buffer_end - buffer) == NULL) + return tree_error("Failed to parse tree. Object is corrupted"); entry->filename = git__strdup(buffer); entry->filename_len = strlen(buffer); @@ -171,11 +272,11 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf buffer++; - git_oid_mkraw(&entry->oid, (const unsigned char *)buffer); + git_oid_fromraw(&entry->oid, (const unsigned char *)buffer); buffer += GIT_OID_RAWSZ; } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse buffer"); + return 0; } int git_tree__parse(git_tree *tree, git_odb_object *obj) @@ -184,100 +285,169 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj) return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len); } -static int write_index_entry(char *buffer, int mode, const char *path, size_t path_len, const git_oid *oid) +static unsigned int find_next_dir(const char *dirname, git_index *index, unsigned int start) { - int written; - written = sprintf(buffer, "%o %.*s%c", mode, (int)path_len, path, 0); - memcpy(buffer + written, &oid->id, GIT_OID_RAWSZ); - return written + GIT_OID_RAWSZ; + unsigned int i, entries = git_index_entrycount(index); + size_t dirlen; + + dirlen = strlen(dirname); + for (i = start; i < entries; ++i) { + git_index_entry *entry = git_index_get(index, i); + if (strlen(entry->path) < dirlen || + memcmp(entry->path, dirname, dirlen) || + (dirlen > 0 && entry->path[dirlen] != '/')) { + break; + } + } + + return i; } -static int write_index(git_oid *oid, git_index *index, const char *base, int baselen, int entry_no, int maxentries) +static int append_entry(git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes) { - size_t size, offset; - char *buffer; - int nr, error; + git_tree_entry *entry; - /* Guess at some random initial size */ - size = maxentries * 40; - buffer = git__malloc(size); - if (buffer == NULL) - return GIT_ENOMEM; - - offset = 0; - - for (nr = entry_no; nr < maxentries; ++nr) { - git_index_entry *entry = git_index_get(index, nr); + entry = git__calloc(1, sizeof(git_tree_entry)); + GITERR_CHECK_ALLOC(entry); - const char *pathname = entry->path, *filename, *dirname; - int pathlen = strlen(pathname), entrylen; + entry->filename = git__strdup(filename); + entry->filename_len = strlen(entry->filename); - unsigned int write_mode; - git_oid subtree_oid; - git_oid *write_oid; - - /* Did we hit the end of the directory? Return how many we wrote */ - if (baselen >= pathlen || memcmp(base, pathname, baselen) != 0) - break; - - /* Do we have _further_ subdirectories? */ - filename = pathname + baselen; - dirname = strchr(filename, '/'); + git_oid_cpy(&entry->oid, id); + entry->attr = attributes; - write_oid = &entry->oid; - write_mode = entry->mode; + if (git_vector_insert(&bld->entries, entry) < 0) + return -1; - if (dirname) { - int subdir_written; + return 0; +} -#if 0 - if (entry->mode != S_IFDIR) { - free(buffer); - return GIT_EOBJCORRUPTED; - } -#endif - subdir_written = write_index(&subtree_oid, index, pathname, dirname - pathname + 1, nr, maxentries); +static int write_tree( + git_oid *oid, + git_repository *repo, + git_index *index, + const char *dirname, + unsigned int start) +{ + git_treebuilder *bld = NULL; - if (subdir_written < GIT_SUCCESS) { - free(buffer); - return subdir_written; - } - - nr = subdir_written - 1; - - /* Now we need to write out the directory entry into this tree.. */ - pathlen = dirname - pathname; - write_oid = &subtree_oid; - write_mode = S_IFDIR; - } + unsigned int i, entries = git_index_entrycount(index); + int error; + size_t dirname_len = strlen(dirname); + const git_tree_cache *cache; - entrylen = pathlen - baselen; - if (offset + entrylen + 32 > size) { - size = alloc_nr(offset + entrylen + 32); - buffer = git__realloc(buffer, size); - - if (buffer == NULL) - return GIT_ENOMEM; - } - - offset += write_index_entry(buffer + offset, write_mode, filename, entrylen, write_oid); + cache = git_tree_cache_get(index->tree, dirname); + if (cache != NULL && cache->entries >= 0){ + git_oid_cpy(oid, &cache->oid); + return find_next_dir(dirname, index, start); } - - error = git_odb_write(oid, index->repository->db, buffer, offset, GIT_OBJ_TREE); - free(buffer); - return (error == GIT_SUCCESS) ? nr : git__rethrow(error, "Failed to write index"); + error = git_treebuilder_create(&bld, NULL); + if (bld == NULL) { + return -1; + } + + /* + * This loop is unfortunate, but necessary. The index doesn't have + * any directores, so we need to handle that manually, and we + * need to keep track of the current position. + */ + for (i = start; i < entries; ++i) { + git_index_entry *entry = git_index_get(index, i); + char *filename, *next_slash; + + /* + * If we've left our (sub)tree, exit the loop and return. The + * first check is an early out (and security for the + * third). The second check is a simple prefix comparison. The + * third check catches situations where there is a directory + * win32/sys and a file win32mmap.c. Without it, the following + * code believes there is a file win32/mmap.c + */ + if (strlen(entry->path) < dirname_len || + memcmp(entry->path, dirname, dirname_len) || + (dirname_len > 0 && entry->path[dirname_len] != '/')) { + break; + } + + filename = entry->path + dirname_len; + if (*filename == '/') + filename++; + next_slash = strchr(filename, '/'); + if (next_slash) { + git_oid sub_oid; + int written; + char *subdir, *last_comp; + + subdir = git__strndup(entry->path, next_slash - entry->path); + GITERR_CHECK_ALLOC(subdir); + + /* Write out the subtree */ + written = write_tree(&sub_oid, repo, index, subdir, i); + if (written < 0) { + tree_error("Failed to write subtree"); + goto on_error; + } else { + i = written - 1; /* -1 because of the loop increment */ + } + + /* + * We need to figure out what we want toinsert + * into this tree. If we're traversing + * deps/zlib/, then we only want to write + * 'zlib' into the tree. + */ + last_comp = strrchr(subdir, '/'); + if (last_comp) { + last_comp++; /* Get rid of the '/' */ + } else { + last_comp = subdir; + } + error = append_entry(bld, last_comp, &sub_oid, S_IFDIR); + git__free(subdir); + if (error < 0) { + tree_error("Failed to insert dir"); + goto on_error; + } + } else { + error = append_entry(bld, filename, &entry->oid, entry->mode); + if (error < 0) { + tree_error("Failed to insert file"); + goto on_error; + } + } + } + + if (git_treebuilder_write(oid, repo, bld) < 0) + goto on_error; + + git_treebuilder_free(bld); + return i; + +on_error: + git_treebuilder_free(bld); + return -1; } int git_tree_create_fromindex(git_oid *oid, git_index *index) { - int error; + int ret; + git_repository *repo; - if (index->repository == NULL) - return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository"); + repo = (git_repository *)GIT_REFCOUNT_OWNER(index); - error = write_index(oid, index, "", 0, 0, git_index_entrycount(index)); - return (error < GIT_SUCCESS) ? git__rethrow(error, "Failed to create tree") : GIT_SUCCESS; + if (repo == NULL) + return tree_error("Failed to create tree. " + "The index file is not backed up by an existing repository"); + + if (index->tree != NULL && index->tree->entries >= 0) { + git_oid_cpy(oid, &index->tree->oid); + return 0; + } + + /* The tree cache didn't help us */ + ret = write_tree(oid, repo, index, "", 0); + return ret < 0 ? ret : 0; } static void sort_entries(git_treebuilder *bld) @@ -288,51 +458,34 @@ static void sort_entries(git_treebuilder *bld) int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld; - size_t i, source_entries = DEFAULT_TREE_SIZE; + unsigned int i, source_entries = DEFAULT_TREE_SIZE; assert(builder_p); bld = git__calloc(1, sizeof(git_treebuilder)); - if (bld == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(bld); if (source != NULL) source_entries = source->entries.length; - if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < GIT_SUCCESS) { - free(bld); - return GIT_ENOMEM; - } + if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0) + goto on_error; if (source != NULL) { - bld->entry_count = source_entries; for (i = 0; i < source->entries.length; ++i) { git_tree_entry *entry_src = source->entries.contents[i]; - git_tree_entry *entry = git__calloc(1, sizeof(git_tree_entry)); - if (entry == NULL) { - git_treebuilder_free(bld); - return GIT_ENOMEM; - } - - entry->filename = git__strdup(entry_src->filename); - - if (entry->filename == NULL) { - free(entry); - git_treebuilder_free(bld); - return GIT_ENOMEM; - } - - entry->filename_len = entry_src->filename_len; - git_oid_cpy(&entry->oid, &entry_src->oid); - entry->attr = entry_src->attr; - - git_vector_insert(&bld->entries, entry); + if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0) + goto on_error; } } *builder_p = bld; - return GIT_SUCCESS; + return 0; + +on_error: + git_treebuilder_free(bld); + return -1; } int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes) @@ -343,23 +496,23 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con assert(bld && id && filename); if (!valid_attributes(attributes)) - return git__throw(GIT_ERROR, "Failed to insert entry. Invalid atrributes"); + return tree_error("Failed to insert entry. Invalid attributes"); - if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename)) != GIT_ENOTFOUND) { + if (!valid_entry_name(filename)) + return tree_error("Failed to insert entry. Invalid name for a tree entry"); + + pos = tree_key_search(&bld->entries, filename); + + if (pos >= 0) { entry = git_vector_get(&bld->entries, pos); - if (entry->removed) { + if (entry->removed) entry->removed = 0; - bld->entry_count++; - } } else { - if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL) - return GIT_ENOMEM; + entry = git__calloc(1, sizeof(git_tree_entry)); + GITERR_CHECK_ALLOC(entry); - memset(entry, 0x0, sizeof(git_tree_entry)); entry->filename = git__strdup(filename); entry->filename_len = strlen(entry->filename); - - bld->entry_count++; } git_oid_cpy(&entry->oid, id); @@ -367,25 +520,24 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con if (pos == GIT_ENOTFOUND) { if (git_vector_insert(&bld->entries, entry) < 0) - return GIT_ENOMEM; + return -1; } if (entry_out != NULL) *entry_out = entry; - return GIT_SUCCESS; + return 0; } -const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { int idx; git_tree_entry *entry; assert(bld && filename); - sort_entries(bld); - idx = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename); - if (idx == GIT_ENOTFOUND) + idx = tree_key_search(&bld->entries, filename); + if (idx < 0) return NULL; entry = git_vector_get(&bld->entries, idx); @@ -395,65 +547,67 @@ const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *file return entry; } +const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +{ + return treebuilder_get(bld, filename); +} + int git_treebuilder_remove(git_treebuilder *bld, const char *filename) { - git_tree_entry *remove_ptr = (git_tree_entry *)git_treebuilder_get(bld, filename); + git_tree_entry *remove_ptr = treebuilder_get(bld, filename); if (remove_ptr == NULL || remove_ptr->removed) - return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree"); + return tree_error("Failed to remove entry. File isn't in the tree"); remove_ptr->removed = 1; - bld->entry_count--; - return GIT_SUCCESS; + return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { - size_t i, size = 0; - char filemode[MAX_FILEMODE_BYTES + 1 + 1]; - git_odb_stream *stream; - int error; + unsigned int i; + git_buf tree = GIT_BUF_INIT; + git_odb *odb; assert(bld); sort_entries(bld); + /* Grow the buffer beforehand to an estimated size */ + git_buf_grow(&tree, bld->entries.length * 72); + for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *entry = bld->entries.contents[i]; if (entry->removed) continue; - snprintf(filemode, sizeof(filemode), "%o ", entry->attr); - size += strlen(filemode); - size += entry->filename_len + 1; - size += GIT_OID_RAWSZ; + git_buf_printf(&tree, "%o ", entry->attr); + git_buf_put(&tree, entry->filename, entry->filename_len + 1); + git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ); } - if ((error = git_odb_open_wstream(&stream, git_repository_database(repo), size, GIT_OBJ_TREE)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write tree. Can't open write stream"); + if (git_buf_oom(&tree)) + goto on_error; - for (i = 0; i < bld->entries.length; ++i) { - git_tree_entry *entry = bld->entries.contents[i]; + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - if (entry->removed) - continue; - snprintf(filemode, sizeof(filemode), "%o ", entry->attr); - stream->write(stream, filemode, strlen(filemode)); - stream->write(stream, entry->filename, entry->filename_len + 1); - stream->write(stream, (char *)entry->oid.id, GIT_OID_RAWSZ); - } + if (git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE) < 0) + goto on_error; - error = stream->finalize_write(oid, stream); - stream->free(stream); + git_buf_free(&tree); + return 0; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write tree"); +on_error: + git_buf_free(&tree); + return -1; } void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload) { - size_t i; + unsigned int i; assert(bld && filter); @@ -466,13 +620,13 @@ void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_e void git_treebuilder_clear(git_treebuilder *bld) { - size_t i; + unsigned int i; assert(bld); for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *e = bld->entries.contents[i]; - free(e->filename); - free(e); + git__free(e->filename); + git__free(e); } git_vector_clear(&bld->entries); @@ -482,7 +636,150 @@ void git_treebuilder_free(git_treebuilder *bld) { git_treebuilder_clear(bld); git_vector_free(&bld->entries); - free(bld); + git__free(bld); } +static int tree_frompath( + git_tree **parent_out, + git_tree *root, + git_buf *treeentry_path, + size_t offset) +{ + char *slash_pos = NULL; + const git_tree_entry* entry; + int error = 0; + git_tree *subtree; + + if (!*(treeentry_path->ptr + offset)) { + giterr_set(GITERR_INVALID, + "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr); + return -1; + } + + slash_pos = (char *)strchr(treeentry_path->ptr + offset, '/'); + + if (slash_pos == NULL) + return git_tree_lookup( + parent_out, + root->object.repo, + git_object_id((const git_object *)root) + ); + + if (slash_pos == treeentry_path->ptr + offset) { + giterr_set(GITERR_INVALID, + "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr); + return -1; + } + + *slash_pos = '\0'; + + entry = git_tree_entry_byname(root, treeentry_path->ptr + offset); + + if (slash_pos != NULL) + *slash_pos = '/'; + + if (entry == NULL) { + giterr_set(GITERR_TREE, + "No tree entry can be found from " + "the given tree and relative path '%s'.", treeentry_path->ptr); + return GIT_ENOTFOUND; + } + + + if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0) + return error; + + error = tree_frompath( + parent_out, + subtree, + treeentry_path, + (slash_pos - treeentry_path->ptr) + 1 + ); + + git_tree_free(subtree); + return error; +} + +int git_tree_get_subtree( + git_tree **subtree, + git_tree *root, + const char *subtree_path) +{ + int error; + git_buf buffer = GIT_BUF_INIT; + + assert(subtree && root && subtree_path); + + if ((error = git_buf_sets(&buffer, subtree_path)) == 0) + error = tree_frompath(subtree, root, &buffer, 0); + + git_buf_free(&buffer); + + return error; +} + +static int tree_walk_post( + git_tree *tree, + git_treewalk_cb callback, + git_buf *path, + void *payload) +{ + int error = 0; + unsigned int i; + + for (i = 0; i < tree->entries.length; ++i) { + git_tree_entry *entry = tree->entries.contents[i]; + + if (callback(path->ptr, entry, payload) < 0) + continue; + + if (git_tree_entry__is_tree(entry)) { + git_tree *subtree; + size_t path_len = git_buf_len(path); + + if ((error = git_tree_lookup( + &subtree, tree->object.repo, &entry->oid)) < 0) + break; + + /* append the next entry to the path */ + git_buf_puts(path, entry->filename); + git_buf_putc(path, '/'); + + if (git_buf_oom(path)) + return -1; + + if (tree_walk_post(subtree, callback, path, payload) < 0) + return -1; + + git_buf_truncate(path, path_len); + git_tree_free(subtree); + } + } + + return 0; +} + +int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload) +{ + int error = 0; + git_buf root_path = GIT_BUF_INIT; + + switch (mode) { + case GIT_TREEWALK_POST: + error = tree_walk_post(tree, callback, &root_path, payload); + break; + + case GIT_TREEWALK_PRE: + tree_error("Preorder tree walking is still not implemented"); + return -1; + + default: + giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); + return -1; + } + + git_buf_free(&root_path); + + return error; +} diff --git a/src/tree.h b/src/tree.h index bff3f8edb..498a90d66 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_tree_h__ #define INCLUDE_tree_h__ @@ -21,11 +27,25 @@ struct git_tree { struct git_treebuilder { git_vector entries; - size_t entry_count; }; +GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) +{ + return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); +} + void git_tree__free(git_tree *tree); int git_tree__parse(git_tree *tree, git_odb_object *obj); +/** + * Lookup the first position in the tree with a given prefix. + * + * @param tree a previously loaded tree. + * @param prefix the beginning of a path to find in the tree. + * @return index of the first item at or after the given prefix. + */ +int git_tree__prefix_position(git_tree *tree, const char *prefix); + + #endif diff --git a/src/tsort.c b/src/tsort.c new file mode 100644 index 000000000..f54c21e50 --- /dev/null +++ b/src/tsort.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +/** + * An array-of-pointers implementation of Python's Timsort + * Based on code by Christopher Swenson under the MIT license + * + * Copyright (c) 2010 Christopher Swenson + * Copyright (c) 2011 Vicent Marti + */ + +#ifndef MAX +# define MAX(x,y) (((x) > (y) ? (x) : (y))) +#endif + +#ifndef MIN +# define MIN(x,y) (((x) < (y) ? (x) : (y))) +#endif + +typedef int (*cmp_ptr_t)(const void *, const void *); + +static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) +{ + int l, c, r; + void *lx, *cx; + + assert(size > 0); + + l = 0; + r = (int)size - 1; + c = r >> 1; + lx = dst[l]; + + /* check for beginning conditions */ + if (cmp(x, lx) < 0) + return 0; + + else if (cmp(x, lx) == 0) { + int i = 1; + while (cmp(x, dst[i]) == 0) + i++; + return i; + } + + /* guaranteed not to be >= rx */ + cx = dst[c]; + while (1) { + const int val = cmp(x, cx); + if (val < 0) { + if (c - l <= 1) return c; + r = c; + } else if (val > 0) { + if (r - c <= 1) return c + 1; + l = c; + lx = cx; + } else { + do { + cx = dst[++c]; + } while (cmp(x, cx) == 0); + return c; + } + c = l + ((r - l) >> 1); + cx = dst[c]; + } +} + +/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ +static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp) +{ + size_t i; + void *x; + int location; + + for (i = start; i < size; i++) { + int j; + /* If this entry is already correct, just move along */ + if (cmp(dst[i - 1], dst[i]) <= 0) + continue; + + /* Else we need to find the right place, shift everything over, and squeeze in */ + x = dst[i]; + location = binsearch(dst, x, i, cmp); + for (j = (int)i - 1; j >= location; j--) { + dst[j + 1] = dst[j]; + } + dst[location] = x; + } +} + + +/* timsort implementation, based on timsort.txt */ +struct tsort_run { + ssize_t start; + ssize_t length; +}; + +struct tsort_store { + size_t alloc; + cmp_ptr_t cmp; + void **storage; +}; + +static void reverse_elements(void **dst, ssize_t start, ssize_t end) +{ + while (start < end) { + void *tmp = dst[start]; + dst[start] = dst[end]; + dst[end] = tmp; + + start++; + end--; + } +} + +static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +{ + ssize_t curr = start + 2; + + if (size - start == 1) + return 1; + + if (start >= size - 2) { + if (store->cmp(dst[size - 2], dst[size - 1]) > 0) { + void *tmp = dst[size - 1]; + dst[size - 1] = dst[size - 2]; + dst[size - 2] = tmp; + } + + return 2; + } + + if (store->cmp(dst[start], dst[start + 1]) <= 0) { + while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0) + curr++; + + return curr - start; + } else { + while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0) + curr++; + + /* reverse in-place */ + reverse_elements(dst, start, curr - 1); + return curr - start; + } +} + +static size_t compute_minrun(size_t n) +{ + int r = 0; + while (n >= 64) { + r |= n & 1; + n >>= 1; + } + return n + r; +} + +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) +{ + if (stack_curr < 2) + return 1; + + else if (stack_curr == 2) { + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + return (A > B); + } else { + const ssize_t A = stack[stack_curr - 3].length; + const ssize_t B = stack[stack_curr - 2].length; + const ssize_t C = stack[stack_curr - 1].length; + return !((A <= B + C) || (B <= C)); + } +} + +static int resize(struct tsort_store *store, size_t new_size) +{ + if (store->alloc < new_size) { + void **tempstore = git__realloc(store->storage, new_size * sizeof(void *)); + + /** + * Do not propagate on OOM; this will abort the sort and + * leave the array unsorted, but no error code will be + * raised + */ + if (tempstore == NULL) + return -1; + + store->storage = tempstore; + store->alloc = new_size; + } + + return 0; +} + +static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) +{ + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + const ssize_t curr = stack[stack_curr - 2].start; + + void **storage; + ssize_t i, j, k; + + if (resize(store, MIN(A, B)) < 0) + return; + + storage = store->storage; + + /* left merge */ + if (A < B) { + memcpy(storage, &dst[curr], A * sizeof(void *)); + i = 0; + j = curr + A; + + for (k = curr; k < curr + A + B; k++) { + if ((i < A) && (j < curr + A + B)) { + if (store->cmp(storage[i], dst[j]) <= 0) + dst[k] = storage[i++]; + else + dst[k] = dst[j++]; + } else if (i < A) { + dst[k] = storage[i++]; + } else + dst[k] = dst[j++]; + } + } else { + memcpy(storage, &dst[curr + A], B * sizeof(void *)); + i = B - 1; + j = curr + A - 1; + + for (k = curr + A + B - 1; k >= curr; k--) { + if ((i >= 0) && (j >= curr)) { + if (store->cmp(dst[j], storage[i]) > 0) + dst[k] = dst[j--]; + else + dst[k] = storage[i--]; + } else if (i >= 0) + dst[k] = storage[i--]; + else + dst[k] = dst[j--]; + } + } +} + +static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) +{ + ssize_t A, B, C; + + while (1) { + /* if the stack only has one thing on it, we are done with the collapse */ + if (stack_curr <= 1) + break; + + /* if this is the last merge, just do it */ + if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + + /* check if the invariant is off for a stack of 2 elements */ + else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + else if (stack_curr == 2) + break; + + A = stack[stack_curr - 3].length; + B = stack[stack_curr - 2].length; + C = stack[stack_curr - 1].length; + + /* check first invariant */ + if (A <= B + C) { + if (A < C) { + merge(dst, stack, stack_curr - 1, store); + stack[stack_curr - 3].length += stack[stack_curr - 2].length; + stack[stack_curr - 2] = stack[stack_curr - 1]; + stack_curr--; + } else { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } + } else if (B <= C) { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } else + break; + } + + return stack_curr; +} + +#define PUSH_NEXT() do {\ + len = count_run(dst, curr, size, store);\ + run = minrun;\ + if (run < minrun) run = minrun;\ + if (run > (ssize_t)size - curr) run = size - curr;\ + if (run > len) {\ + bisort(&dst[curr], len, run, cmp);\ + len = run;\ + }\ + run_stack[stack_curr].start = curr;\ + run_stack[stack_curr++].length = len;\ + curr += len;\ + if (curr == (ssize_t)size) {\ + /* finish up */ \ + while (stack_curr > 1) { \ + merge(dst, run_stack, stack_curr, store); \ + run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ + stack_curr--; \ + } \ + if (store->storage != NULL) {\ + git__free(store->storage);\ + store->storage = NULL;\ + }\ + return;\ + }\ +}\ +while (0) + +void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) +{ + struct tsort_store _store, *store = &_store; + struct tsort_run run_stack[128]; + + ssize_t stack_curr = 0; + ssize_t len, run; + ssize_t curr = 0; + ssize_t minrun; + + if (size < 64) { + bisort(dst, 1, size, cmp); + return; + } + + /* compute the minimum run length */ + minrun = (ssize_t)compute_minrun(size); + + /* temporary storage for merges */ + store->alloc = 0; + store->storage = NULL; + store->cmp = cmp; + + PUSH_NEXT(); + PUSH_NEXT(); + PUSH_NEXT(); + + while (1) { + if (!check_invariant(run_stack, stack_curr)) { + stack_curr = collapse(dst, run_stack, stack_curr, store, size); + continue; + } + + PUSH_NEXT(); + } +} diff --git a/src/unix/map.c b/src/unix/map.c index 1613152a0..65f4ac91c 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -1,3 +1,12 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 GIT_WIN32 #include "map.h" #ifndef __amigaos4__ @@ -5,19 +14,13 @@ #endif #include - -int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { #ifndef __amigaos4__ int mprot = 0; int mflag = 0; - assert((out != NULL) && (len > 0)); - - if ((out == NULL) || (len == 0)) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length"); - } + GIT_MMAP_VALIDATE(out, len, prot, flags); out->data = NULL; out->len = 0; @@ -26,40 +29,31 @@ int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t o mprot = PROT_WRITE; else if (prot & GIT_PROT_READ) mprot = PROT_READ; - else { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters"); - } if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) mflag = MAP_SHARED; else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) mflag = MAP_PRIVATE; - if (flags & GIT_MAP_FIXED) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set"); + out->data = mmap(NULL, len, mprot, mflag, fd, offset); + if (!out->data || out->data == MAP_FAILED) { + giterr_set(GITERR_OS, "Failed to mmap. Could not write data"); + return -1; } - out->data = mmap(NULL, len, mprot, mflag, fd, offset); - if (!out->data || out->data == MAP_FAILED) - return git__throw(GIT_EOSERR, "Failed to mmap. Could not write data"); out->len = len; #endif - return GIT_SUCCESS; + return 0; } -int git__munmap(git_map *map) +int p_munmap(git_map *map) { #ifndef __amigaos4__ assert(map != NULL); - - if (!map) - return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist"); - munmap(map->data, map->len); #endif - return GIT_SUCCESS; + return 0; } +#endif diff --git a/src/unix/posix.h b/src/unix/posix.h new file mode 100644 index 000000000..48b492941 --- /dev/null +++ b/src/unix/posix.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_posix__w32_h__ +#define INCLUDE_posix__w32_h__ + +#ifndef __sun +# include +# define p_fnmatch(p, s, f) fnmatch(p, s, f) +#else +# include "compat/fnmatch.h" +#endif + +#include + +#define p_lstat(p,b) lstat(p,b) +#define p_readlink(a, b, c) readlink(a, b, c) +#define p_link(o,n) link(o, n) +#define p_unlink(p) unlink(p) +#define p_mkdir(p,m) mkdir(p, m) +#define p_fsync(fd) fsync(fd) +#define p_realpath(p, po) realpath(p, po) +#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) +#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) +#define p_mkstemp(p) mkstemp(p) +#define p_setenv(n,v,o) setenv(n,v,o) + +#endif diff --git a/src/util.c b/src/util.c index 560c40dbb..3093cd767 100644 --- a/src/util.c +++ b/src/util.c @@ -1,22 +1,69 @@ -#define GIT__NO_HIDE_MALLOC +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include #include "common.h" #include #include #include +#include "posix.h" + +#ifdef _MSC_VER +# include +#endif + +void git_libgit2_version(int *major, int *minor, int *rev) +{ + *major = LIBGIT2_VER_MAJOR; + *minor = LIBGIT2_VER_MINOR; + *rev = LIBGIT2_VER_REVISION; +} void git_strarray_free(git_strarray *array) { size_t i; for (i = 0; i < array->count; ++i) - free(array->strings[i]); + git__free(array->strings[i]); - free(array->strings); + git__free(array->strings); } -int git__strtol32(long *result, const char *nptr, const char **endptr, int base) +int git_strarray_copy(git_strarray *tgt, const git_strarray *src) +{ + size_t i; + + assert(tgt && src); + + memset(tgt, 0, sizeof(*tgt)); + + if (!src->count) + return 0; + + tgt->strings = git__calloc(src->count, sizeof(char *)); + GITERR_CHECK_ALLOC(tgt->strings); + + for (i = 0; i < src->count; ++i) { + tgt->strings[tgt->count] = git__strdup(src->strings[i]); + + if (!tgt->strings[tgt->count]) { + git_strarray_free(tgt); + memset(tgt, 0, sizeof(*tgt)); + return -1; + } + + tgt->count++; + } + + return 0; +} + +int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base) { const char *p; - long n, nn; + int64_t n, nn; int c, ovfl, v, neg, ndig; p = nptr; @@ -28,7 +75,7 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base) /* * White space */ - while (isspace(*p)) + while (git__isspace(*p)) p++; /* @@ -78,35 +125,46 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base) } Return: - if (ndig == 0) - return git__throw(GIT_ENOTNUM, "Failed to convert string to long. Not a number"); + if (ndig == 0) { + giterr_set(GITERR_INVALID, "Failed to convert string to long. Not a number"); + return -1; + } if (endptr) *endptr = p; - if (ovfl) - return git__throw(GIT_EOVERFLOW, "Failed to convert string to long. Overflow error"); + if (ovfl) { + giterr_set(GITERR_INVALID, "Failed to convert string to long. Overflow error"); + return -1; + } *result = neg ? -n : n; - return GIT_SUCCESS; + return 0; } -int git__fmt(char *buf, size_t buf_sz, const char *fmt, ...) +int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) { - va_list va; - int r; + int error; + int32_t tmp_int; + int64_t tmp_long; - va_start(va, fmt); - r = vsnprintf(buf, buf_sz, fmt, va); - va_end(va); - if (r < 0 || ((size_t) r) >= buf_sz) - return git__throw(GIT_ERROR, "Failed to format string"); - return r; + if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0) + return error; + + tmp_int = tmp_long & 0xFFFFFFFF; + if (tmp_int != tmp_long) { + giterr_set(GITERR_INVALID, "Failed to convert. '%s' is too large", nptr); + return -1; + } + + *result = tmp_int; + + return error; } -void git__strntolower(char *str, int len) +void git__strntolower(char *str, size_t len) { - int i; + size_t i; for (i = 0; i < len; ++i) { str[i] = (char) tolower(str[i]); @@ -121,7 +179,7 @@ void git__strtolower(char *str) int git__prefixcmp(const char *str, const char *prefix) { for (;;) { - char p = *(prefix++), s; + unsigned char p = *(prefix++), s; if (!p) return 0; if ((s = *(str++)) != p) @@ -138,205 +196,6 @@ int git__suffixcmp(const char *str, const char *suffix) return strcmp(str + (a - b), suffix); } -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git__basename_r(char *buffer, size_t bufflen, const char *path) -{ - const char *endp, *startp; - int len, result; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - startp = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* All slashes becomes "/" */ - if (endp == path && *endp == '/') { - startp = "/"; - len = 1; - goto Exit; - } - - /* Find the start of the base */ - startp = endp; - while (startp > path && *(startp - 1) != '/') - startp--; - - len = endp - startp +1; - -Exit: - result = len; - if (buffer == NULL) { - return result; - } - if (len > (int)bufflen-1) { - len = (int)bufflen-1; - result = GIT_ENOMEM; - } - - if (len >= 0) { - memmove(buffer, startp, len); - buffer[len] = 0; - } - return result; -} - -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git__dirname_r(char *buffer, size_t bufflen, const char *path) -{ - const char *endp; - int result, len; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - path = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* Find the start of the dir */ - while (endp > path && *endp != '/') - endp--; - - /* Either the dir is "/" or there are no slashes */ - if (endp == path) { - path = (*endp == '/') ? "/" : "."; - len = 1; - goto Exit; - } - - do { - endp--; - } while (endp > path && *endp == '/'); - - len = endp - path +1; - -Exit: - result = len; - if (len+1 > GIT_PATH_MAX) { - return GIT_ENOMEM; - } - if (buffer == NULL) - return result; - - if (len > (int)bufflen-1) { - len = (int)bufflen-1; - result = GIT_ENOMEM; - } - - if (len >= 0) { - memmove(buffer, path, len); - buffer[len] = 0; - } - return result; -} - - -char *git__dirname(const char *path) -{ - char *dname = NULL; - int len; - - len = (path ? strlen(path) : 0) + 2; - dname = (char *)git__malloc(len); - if (dname == NULL) - return NULL; - - if (git__dirname_r(dname, len, path) < GIT_SUCCESS) { - free(dname); - return NULL; - } - - return dname; -} - -char *git__basename(const char *path) -{ - char *bname = NULL; - int len; - - len = (path ? strlen(path) : 0) + 2; - bname = (char *)git__malloc(len); - if (bname == NULL) - return NULL; - - if (git__basename_r(bname, len, path) < GIT_SUCCESS) { - free(bname); - return NULL; - } - - return bname; -} - - -const char *git__topdir(const char *path) -{ - size_t len; - int i; - - assert(path); - len = strlen(path); - - if (!len || path[len - 1] != '/') - return NULL; - - for (i = len - 2; i >= 0; --i) - if (path[i] == '/') - break; - - return &path[i + 1]; -} - -void git__joinpath_n(char *buffer_out, int count, ...) -{ - va_list ap; - int i; - char *buffer_start = buffer_out; - - va_start(ap, count); - for (i = 0; i < count; ++i) { - const char *path; - int len; - - path = va_arg(ap, const char *); - - assert((i == 0) || path != buffer_start); - - if (i > 0 && *path == '/' && buffer_out > buffer_start && buffer_out[-1] == '/') - path++; - - if (!*path) - continue; - - len = strlen(path); - memmove(buffer_out, path, len); - buffer_out = buffer_out + len; - - if (i < count - 1 && buffer_out[-1] != '/') - *buffer_out++ = '/'; - } - va_end(ap); - - *buffer_out = '\0'; -} - char *git__strtok(char **end, const char *sep) { char *ptr = *end; @@ -393,7 +252,7 @@ void git__hexdump(const char *buffer, size_t len) printf("%02X ", (unsigned char)*line & 0xFF); for (j = 0; j < (LINE_WIDTH - last_line); ++j) - printf(" "); + printf(" "); printf("| "); @@ -419,22 +278,22 @@ uint32_t git__hash(const void *key, int len, unsigned int seed) while(len >= 4) { uint32_t k = *(uint32_t *)data; - k *= m; - k ^= k >> r; - k *= m; - - h *= m; + k *= m; + k ^= k >> r; + k *= m; + + h *= m; h ^= k; data += 4; len -= 4; } - + switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; - h *= m; + h *= m; }; h ^= h >> 13; @@ -442,7 +301,7 @@ uint32_t git__hash(const void *key, int len, unsigned int seed) h ^= h >> 15; return h; -} +} #else /* Cross-platform version of Murmurhash3 @@ -455,13 +314,13 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) { #define MURMUR_BLOCK() {\ - k1 *= c1; \ - k1 = git__rotl(k1,11);\ - k1 *= c2;\ - h1 ^= k1;\ - h1 = h1*3 + 0x52dce729;\ - c1 = c1*5 + 0x7b7d159c;\ - c2 = c2*5 + 0x6bce6396;\ + k1 *= c1; \ + k1 = git__rotl(k1,11);\ + k1 *= c2;\ + h1 ^= k1;\ + h1 = h1*3 + 0x52dce729;\ + c1 = c1*5 + 0x7b7d159c;\ + c2 = c2*5 + 0x6bce6396;\ } const uint8_t *data = (const uint8_t*)key; @@ -500,5 +359,79 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) h1 ^= h1 >> 16; return h1; -} +} #endif + +/** + * A modified `bsearch` from the BSD glibc. + * + * Copyright (c) 1990 Regents of the University of California. + * All rights reserved. + */ +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) +{ + unsigned int lim; + int cmp = -1; + void **part, **base = array; + + for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : -1; +} + +/** + * A strcmp wrapper + * + * We don't want direct pointers to the CRT on Windows, we may + * get stdcall conflicts. + */ +int git__strcmp_cb(const void *a, const void *b) +{ + const char *stra = (const char *)a; + const char *strb = (const char *)b; + + return strcmp(stra, strb); +} + +int git__parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL) { + *out = 1; + return 0; + } + + if (!strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return 0; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off")) { + *out = 0; + return 0; + } + + return -1; +} diff --git a/src/util.h b/src/util.h index 72e3a9c68..c6851ac7e 100644 --- a/src/util.h +++ b/src/util.h @@ -1,11 +1,20 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_util_h__ #define INCLUDE_util_h__ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) #define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif -/* +/* * Custom memory allocation wrappers * that set error code and error message * on allocation failure @@ -13,24 +22,21 @@ GIT_INLINE(void *) git__malloc(size_t len) { void *ptr = malloc(len); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); + if (!ptr) giterr_set_oom(); return ptr; } GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize) { void *ptr = calloc(nelem, elsize); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); + if (!ptr) giterr_set_oom(); return ptr; } GIT_INLINE(char *) git__strdup(const char *str) { char *ptr = strdup(str); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + if (!ptr) giterr_set_oom(); return ptr; } @@ -43,12 +49,14 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) if (n < length) length = n; - ptr = malloc(length + 1); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + ptr = (char*)malloc(length + 1); + if (!ptr) { + giterr_set_oom(); + return NULL; + } memcpy(ptr, str, length); - ptr[length] = 0; + ptr[length] = '\0'; return ptr; } @@ -56,75 +64,21 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) GIT_INLINE(void *) git__realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); - if (!new_ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); + if (!new_ptr) giterr_set_oom(); return new_ptr; } -extern int git__fmt(char *, size_t, const char *, ...) - GIT_FORMAT_PRINTF(3, 4); +#define git__free(ptr) free(ptr) + extern int git__prefixcmp(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); -extern int git__strtol32(long *n, const char *buff, const char **end_buf, int base); - -/* - * The dirname() function shall take a pointer to a character string - * that contains a pathname, and return a pointer to a string that is a - * pathname of the parent directory of that file. Trailing '/' characters - * in the path are not counted as part of the path. - * - * If path does not contain a '/', then dirname() shall return a pointer to - * the string ".". If path is a null pointer or points to an empty string, - * dirname() shall return a pointer to the string "." . - * - * The `git__dirname` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git__dirname_r` implementation expects a string allocated - * by the user with big enough size. - */ -extern char *git__dirname(const char *path); -extern int git__dirname_r(char *buffer, size_t bufflen, const char *path); - -/* - * This function returns the basename of the file, which is the last - * part of its full name given by fname, with the drive letter and - * leading directories stripped off. For example, the basename of - * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. - * - * Trailing slashes and backslashes are significant: the basename of - * c:/foo/bar/ is an empty string after the rightmost slash. - * - * The `git__basename` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git__basename_r` implementation expects a string allocated - * by the user with big enough size. - */ -extern char *git__basename(const char *path); -extern int git__basename_r(char *buffer, size_t bufflen, const char *path); - -extern const char *git__topdir(const char *path); - -/** - * Join two paths together. Takes care of properly fixing the - * middle slashes and everything - * - * The paths are joined together into buffer_out; this is expected - * to be an user allocated buffer of `GIT_PATH_MAX` size - */ -extern void git__joinpath_n(char *buffer_out, int npath, ...); - -GIT_INLINE(void) git__joinpath(char *buffer_out, const char *path_a, const char *path_b) -{ - git__joinpath_n(buffer_out, 2, path_a, path_b); -} +extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); +extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); extern void git__hexdump(const char *buffer, size_t n); extern uint32_t git__hash(const void *key, int len, uint32_t seed); - /** @return true if p fits into the range of a size_t */ GIT_INLINE(int) git__is_sizet(git_off_t p) { @@ -141,30 +95,132 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) extern char *git__strtok(char **end, const char *sep); -extern void git__strntolower(char *str, int len); +extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); -#define STRLEN(str) (sizeof(str) - 1) +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} -#define GIT_OID_LINE_LENGTH(header) (STRLEN(header) + 1 + GIT_OID_HEXSZ + 1) +extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *)); + +/** + * @param position If non-NULL, this will be set to the position where the + * element is or would be inserted if not found. + * @return pos (>=0) if found or -1 if not found + */ +extern int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position); + +extern int git__strcmp_cb(const void *a, const void *b); + +typedef struct { + short refcount; + void *owner; +} git_refcount; + +typedef void (*git_refcount_freeptr)(void *r); + +#define GIT_REFCOUNT_INC(r) { \ + ((git_refcount *)(r))->refcount++; \ +} + +#define GIT_REFCOUNT_DEC(_r, do_free) { \ + git_refcount *r = (git_refcount *)(_r); \ + r->refcount--; \ + if (r->refcount <= 0 && r->owner == NULL) { do_free(_r); } \ +} + +#define GIT_REFCOUNT_OWN(r, o) { \ + ((git_refcount *)(r))->owner = o; \ +} + +#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner) + +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 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ +}; + +GIT_INLINE(int) git__fromhex(char h) +{ + return from_hex[(unsigned char) h]; +} + +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; i> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return v; +} + +GIT_INLINE(size_t) git__size_t_powerof2(size_t v) +{ + return git__size_t_bitmask(v) + 1; +} + +GIT_INLINE(bool) git__isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__iswildcard(int c) +{ + return (c == '*' || c == '?' || c == '['); +} /* - * Realloc the buffer pointed at by variable 'x' so that it can hold - * at least 'nr' entries; the number of entries currently allocated - * is 'alloc', using the standard growing factor alloc_nr() macro. + * Parse a string value as a boolean, just like Core Git + * does. * - * DO NOT USE any expression with side-effect for 'x' or 'alloc'. + * Valid values for true are: 'true', 'yes', 'on' + * Valid values for false are: 'false', 'no', 'off' */ -#define alloc_nr(x) (((x)+16)*3/2) -#define ALLOC_GROW(x, nr, alloc) \ - do { \ - if ((nr) > alloc) { \ - if (alloc_nr(alloc) < (nr)) \ - alloc = (nr); \ - else \ - alloc = alloc_nr(alloc); \ - x = xrealloc((x), alloc * sizeof(*(x))); \ - } \ - } while (0) +extern int git__parse_bool(int *out, const char *value); #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 1ddc26e3e..6f9aacccf 100644 --- a/src/vector.c +++ b/src/vector.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" @@ -28,7 +10,7 @@ #include "vector.h" static const double resize_factor = 1.75; -static const size_t minimum_size = 8; +static const unsigned int minimum_size = 8; static int resize_vector(git_vector *v) { @@ -36,18 +18,21 @@ static int resize_vector(git_vector *v) if (v->_alloc_size < minimum_size) v->_alloc_size = minimum_size; - v->contents = realloc(v->contents, v->_alloc_size * sizeof(void *)); - if (v->contents == NULL) - return GIT_ENOMEM; + v->contents = git__realloc(v->contents, v->_alloc_size * sizeof(void *)); + GITERR_CHECK_ALLOC(v->contents); - return GIT_SUCCESS; + return 0; } - void git_vector_free(git_vector *v) { assert(v); - free(v->contents); + + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; } int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp) @@ -61,30 +46,63 @@ int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp v->_alloc_size = initial_size; v->_cmp = cmp; - + v->length = 0; v->sorted = 1; v->contents = git__malloc(v->_alloc_size * sizeof(void *)); - if (v->contents == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(v->contents); - return GIT_SUCCESS; + return 0; } int git_vector_insert(git_vector *v, void *element) { assert(v); - if (v->length >= v->_alloc_size) { - if (resize_vector(v) < 0) - return GIT_ENOMEM; - } + if (v->length >= v->_alloc_size && + resize_vector(v) < 0) + return -1; v->contents[v->length++] = element; v->sorted = 0; - return GIT_SUCCESS; + return 0; +} + +int git_vector_insert_sorted( + git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +{ + int result; + size_t pos; + + assert(v && v->_cmp); + + if (!v->sorted) + git_vector_sort(v); + + if (v->length >= v->_alloc_size && + resize_vector(v) < 0) + return -1; + + /* If we find the element and have a duplicate handler callback, + * invoke it. If it returns non-zero, then cancel insert, otherwise + * proceed with normal insert. + */ + if (git__bsearch(v->contents, v->length, element, v->_cmp, &pos) >= 0 && + on_dup != NULL && + (result = on_dup(&v->contents[pos], element)) < 0) + return result; + + /* shift elements to the right */ + if (pos < v->length) { + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + } + + v->contents[pos] = element; + v->length++; + return 0; } void git_vector_sort(git_vector *v) @@ -94,30 +112,36 @@ void git_vector_sort(git_vector *v) if (v->sorted || v->_cmp == NULL) return; - qsort(v->contents, v->length, sizeof(void *), v->_cmp); + git__tsort(v->contents, v->length, v->_cmp); v->sorted = 1; } -int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key) +int git_vector_bsearch3( + unsigned int *at_pos, + git_vector *v, + git_vector_cmp key_lookup, + const void *key) { - void **find; + int rval; + size_t pos; assert(v && key && key_lookup); /* need comparison function to sort the vector */ - if (v->_cmp == NULL) - return git__throw(GIT_ENOTFOUND, "Can't sort vector. No comparison function set"); + assert(v->_cmp != NULL); git_vector_sort(v); - find = bsearch(key, v->contents, v->length, sizeof(void *), key_lookup); - if (find != NULL) - return (int)(find - v->contents); + rval = git__bsearch(v->contents, v->length, key, key_lookup, &pos); - return git__throw(GIT_ENOTFOUND, "Can't find element"); + if (at_pos != NULL) + *at_pos = (unsigned int)pos; + + return (rval >= 0) ? (int)pos : GIT_ENOTFOUND; } -int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key) +int git_vector_search2( + git_vector *v, git_vector_cmp key_lookup, const void *key) { unsigned int i; @@ -128,7 +152,7 @@ int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key return i; } - return git__throw(GIT_ENOTFOUND, "Can't find element"); + return GIT_ENOTFOUND; } static int strict_comparison(const void *a, const void *b) @@ -141,11 +165,6 @@ int git_vector_search(git_vector *v, const void *entry) return git_vector_search2(v, v->_cmp ? v->_cmp : strict_comparison, entry); } -int git_vector_bsearch(git_vector *v, const void *key) -{ - return git_vector_bsearch2(v, v->_cmp, key); -} - int git_vector_remove(git_vector *v, unsigned int idx) { unsigned int i; @@ -153,13 +172,39 @@ int git_vector_remove(git_vector *v, unsigned int idx) assert(v); if (idx >= v->length || v->length == 0) - return git__throw(GIT_ENOTFOUND, "Can't remove element. Index out of bounds"); + return GIT_ENOTFOUND; for (i = idx; i < v->length - 1; ++i) v->contents[i] = v->contents[i + 1]; v->length--; - return GIT_SUCCESS; + return 0; +} + +void git_vector_pop(git_vector *v) +{ + if (v->length > 0) + v->length--; +} + +void git_vector_uniq(git_vector *v) +{ + git_vector_cmp cmp; + unsigned int i, j; + + if (v->length <= 1) + return; + + git_vector_sort(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])) + v->contents[i] = v->contents[j]; + else + v->contents[++i] = v->contents[j]; + + v->length -= j - i - 1; } void git_vector_clear(git_vector *v) @@ -169,4 +214,14 @@ void git_vector_clear(git_vector *v) v->sorted = 1; } +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + if (!a || !b || a == b) + return; + + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); +} diff --git a/src/vector.h b/src/vector.h index 256452ee5..9139db345 100644 --- a/src/vector.h +++ b/src/vector.h @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_vector_h__ #define INCLUDE_vector_h__ @@ -13,24 +19,60 @@ typedef struct git_vector { int sorted; } git_vector; +#define GIT_VECTOR_INIT {0} + int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); +void git_vector_swap(git_vector *a, git_vector *b); + +void git_vector_sort(git_vector *v); int git_vector_search(git_vector *v, const void *entry); int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key); -int git_vector_bsearch(git_vector *v, const void *entry); -int git_vector_bsearch2(git_vector *v, git_vector_cmp cmp, const void *key); +int git_vector_bsearch3( + unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); -void git_vector_sort(git_vector *v); +GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key) +{ + return git_vector_bsearch3(NULL, v, v->_cmp, key); +} + +GIT_INLINE(int) git_vector_bsearch2( + git_vector *v, git_vector_cmp cmp, const void *key) +{ + return git_vector_bsearch3(NULL, v, cmp, key); +} GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) { return (position < v->length) ? v->contents[position] : NULL; } +GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position) +{ + return (position < v->length) ? v->contents[position] : NULL; +} + +#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) + +GIT_INLINE(void *) git_vector_last(git_vector *v) +{ + return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; +} + +#define git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- ) + int git_vector_insert(git_vector *v, void *element); +int git_vector_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, unsigned int idx); +void git_vector_pop(git_vector *v); +void git_vector_uniq(git_vector *v); #endif diff --git a/src/win32/dir.c b/src/win32/dir.c index 069a41c3a..bc3d40fa5 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -1,5 +1,13 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * 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 "git2/windows.h" static int init_filter(char *filter, size_t n, const char *dir) { @@ -19,7 +27,8 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[4096]; - git__DIR *new; + wchar_t* filter_w = NULL; + git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) return NULL; @@ -28,71 +37,119 @@ git__DIR *git__opendir(const char *dir) if (!new) return NULL; - new->dir = git__malloc(strlen(dir)+1); - if (!new->dir) { - free(new); - return NULL; - } - strcpy(new->dir, dir); + new->dir = git__strdup(dir); + if (!new->dir) + goto fail; + + filter_w = gitwin_to_utf16(filter); + if (!filter_w) + goto fail; + + new->h = FindFirstFileW(filter_w, &new->f); + git__free(filter_w); - new->h = FindFirstFile(filter, &new->f); if (new->h == INVALID_HANDLE_VALUE) { - free(new->dir); - free(new); - return NULL; + giterr_set(GITERR_OS, "Could not open directory '%s'", dir); + goto fail; } - new->first = 1; + new->first = 1; return new; + +fail: + git__free(new->dir); + git__free(new); + return NULL; +} + +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) +{ + if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) + return -1; + + *result = NULL; + + if (d->first) + d->first = 0; + else if (!FindNextFileW(d->h, &d->f)) { + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + giterr_set(GITERR_OS, "Could not read from directory '%s'", d->dir); + return -1; + } + + if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) + return -1; + + entry->d_ino = 0; + + if (WideCharToMultiByte( + gitwin_get_codepage(), 0, d->f.cFileName, -1, + entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0) + { + giterr_set(GITERR_OS, "Could not convert filename to UTF-8"); + return -1; + } + + *result = entry; + + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + + return 0; } struct git__dirent *git__readdir(git__DIR *d) { - if (!d || d->h == INVALID_HANDLE_VALUE) + struct git__dirent *result; + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) return NULL; - - if (d->first) - d->first = 0; - else { - if (!FindNextFile(d->h, &d->f)) - return NULL; - } - - if (strlen(d->f.cFileName) >= sizeof(d->entry.d_name)) - return NULL; - - d->entry.d_ino = 0; - strcpy(d->entry.d_name, d->f.cFileName); - - return &d->entry; + return result; } void git__rewinddir(git__DIR *d) { char filter[4096]; + wchar_t* filter_w; - if (d) { - if (d->h != INVALID_HANDLE_VALUE) - FindClose(d->h); + if (!d) + return; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); d->h = INVALID_HANDLE_VALUE; d->first = 0; - if (init_filter(filter, sizeof(filter), d->dir)) { - d->h = FindFirstFile(filter, &d->f); - if (d->h != INVALID_HANDLE_VALUE) - d->first = 1; - } } + + if (!init_filter(filter, sizeof(filter), d->dir) || + (filter_w = gitwin_to_utf16(filter)) == NULL) + return; + + d->h = FindFirstFileW(filter_w, &d->f); + git__free(filter_w); + + if (d->h == INVALID_HANDLE_VALUE) + giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir); + else + d->first = 1; } int git__closedir(git__DIR *d) { - if (d) { - if (d->h != INVALID_HANDLE_VALUE) - FindClose(d->h); - if (d->dir) - free(d->dir); - free(d); + if (!d) + return 0; + + if (d->h != INVALID_HANDLE_VALUE) { + 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 new file mode 100644 index 000000000..c816d79bb --- /dev/null +++ b/src/win32/dir.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_dir_h__ +#define INCLUDE_dir_h__ + +#include "common.h" + +struct git__dirent { + int d_ino; + char d_name[261]; +}; + +typedef struct { + HANDLE h; + WIN32_FIND_DATAW f; + struct git__dirent entry; + char *dir; + int first; +} git__DIR; + +extern git__DIR *git__opendir(const char *); +extern struct git__dirent *git__readdir(git__DIR *); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); +extern void git__rewinddir(git__DIR *); +extern int git__closedir(git__DIR *); + +# ifndef GIT__WIN32_NO_WRAP_DIR +# define dirent git__dirent +# define DIR git__DIR +# define opendir git__opendir +# define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) +# define rewinddir git__rewinddir +# define closedir git__closedir +# endif + +#endif /* INCLUDE_dir_h__ */ diff --git a/src/win32/fileops.c b/src/win32/fileops.c deleted file mode 100644 index d435e706e..000000000 --- a/src/win32/fileops.c +++ /dev/null @@ -1,41 +0,0 @@ -#define GIT__WIN32_NO_HIDE_FILEOPS -#include "fileops.h" -#include - -int git__unlink(const char *path) -{ - chmod(path, 0666); - return unlink(path); -} - -int git__mkstemp(char *template) -{ - char *file = mktemp(template); - if (file == NULL) - return -1; - return open(file, O_RDWR | O_CREAT | O_BINARY, 0600); -} - -int git__fsync(int fd) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - - if (!FlushFileBuffers(fh)) { - DWORD code = GetLastError(); - - if (code == ERROR_INVALID_HANDLE) - errno = EINVAL; - else - errno = EIO; - - return -1; - } - - return 0; -} - diff --git a/src/win32/git2.rc b/src/win32/git2.rc new file mode 100644 index 000000000..3a65c0a0f --- /dev/null +++ b/src/win32/git2.rc @@ -0,0 +1,42 @@ +#include +#include "../../include/git2/version.h" + +#ifndef INCLUDE_LIB +#define LIBGIT2_FILENAME "git2.dll" +#else +#define LIBGIT2_FILENAME "libgit2.dll" +#endif + +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0 + PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS 1 +#else + FILEFLAGS 0 +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "libgit2 - the Git linkable library\0" + VALUE "FileVersion", LIBGIT2_VERSION "\0" + VALUE "InternalName", LIBGIT2_FILENAME "\0" + VALUE "LegalCopyright", "Copyright (C) 2009-2012 the libgit2 contributors\0" + VALUE "OriginalFilename", LIBGIT2_FILENAME "\0" + VALUE "ProductName", "libgit2\0" + VALUE "ProductVersion", LIBGIT2_VERSION "\0" + VALUE "Comments", "For more information visit http://libgit2.github.com/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/src/win32/map.c b/src/win32/map.c index c7a39fcf6..f730120cc 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -1,3 +1,9 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "map.h" #include @@ -16,23 +22,18 @@ static DWORD get_page_size(void) return page_size; } -int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); DWORD page_size = get_page_size(); DWORD fmap_prot = 0; DWORD view_prot = 0; DWORD off_low = 0; - DWORD off_hi = 0; + DWORD off_hi = 0; git_off_t page_start; git_off_t page_offset; - assert((out != NULL) && (len > 0)); - - if ((out == NULL) || (len == 0)) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length"); - } + GIT_MMAP_VALIDATE(out, len, prot, flags); out->data = NULL; out->len = 0; @@ -40,86 +41,75 @@ int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t o if (fh == INVALID_HANDLE_VALUE) { errno = EBADF; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value"); + giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value"); + return -1; } if (prot & GIT_PROT_WRITE) fmap_prot |= PAGE_READWRITE; else if (prot & GIT_PROT_READ) fmap_prot |= PAGE_READONLY; - else { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters"); - } if (prot & GIT_PROT_WRITE) view_prot |= FILE_MAP_WRITE; if (prot & GIT_PROT_READ) view_prot |= FILE_MAP_READ; - if (flags & GIT_MAP_FIXED) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set"); - } - page_start = (offset / page_size) * page_size; page_offset = offset - page_start; - if (page_offset != 0) { /* offset must be multiple of page size */ + if (page_offset != 0) { /* offset must be multiple of page size */ errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Offset must be multiple of page size"); + giterr_set(GITERR_OS, "Failed to mmap. Offset must be multiple of page size"); + return -1; } out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { - /* errno = ? */ + giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value"); out->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value"); + return -1; } assert(sizeof(git_off_t) == 8); + off_low = (DWORD)(page_start); off_hi = (DWORD)(page_start >> 32); out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); if (!out->data) { - /* errno = ? */ + giterr_set(GITERR_OS, "Failed to mmap. No data written"); CloseHandle(out->fmh); out->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to mmap. No data written"); + return -1; } out->len = len; - return GIT_SUCCESS; + return 0; } -int git__munmap(git_map *map) +int p_munmap(git_map *map) { - assert(map != NULL); + int error = 0; - if (!map) - return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist"); + assert(map != NULL); if (map->data) { if (!UnmapViewOfFile(map->data)) { - /* errno = ? */ - CloseHandle(map->fmh); - map->data = NULL; - map->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to munmap. Could not unmap view of file"); + giterr_set(GITERR_OS, "Failed to munmap. Could not unmap view of file"); + error = -1; } map->data = NULL; } if (map->fmh) { if (!CloseHandle(map->fmh)) { - /* errno = ? */ - map->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to munmap. Could not close handle"); + giterr_set(GITERR_OS, "Failed to munmap. Could not close handle"); + error = -1; } map->fmh = NULL; } - return GIT_SUCCESS; + return error; } diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h new file mode 100644 index 000000000..6200dc094 --- /dev/null +++ b/src/win32/mingw-compat.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_mingw_compat__ +#define INCLUDE_mingw_compat__ + +#if defined(__MINGW32__) + +/* use a 64-bit file offset type */ +# define lseek _lseeki64 +# define stat _stati64 +# define fstat _fstati64 + +/* stat: file mode type testing macros */ +# define _S_IFLNK 0120000 +# define S_IFLNK _S_IFLNK +# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) + +#endif + +#endif /* INCLUDE_mingw_compat__ */ diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h new file mode 100644 index 000000000..ccc091cd0 --- /dev/null +++ b/src/win32/msvc-compat.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_msvc_compat__ +#define INCLUDE_msvc_compat__ + +#if defined(_MSC_VER) + +/* access() mode parameter #defines */ +# define F_OK 0 /* existence check */ +# define W_OK 2 /* write mode check */ +# define R_OK 4 /* read mode check */ + +# define lseek _lseeki64 +# define stat _stat64 +# define fstat _fstat64 + +/* stat: file mode type testing macros */ +# define _S_IFLNK 0120000 +# define S_IFLNK _S_IFLNK +# define S_IXUSR 00100 + +# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) + +# define mode_t unsigned short + +/* case-insensitive string comparison */ +# define strcasecmp _stricmp +# define strncasecmp _strnicmp + +/* MSVC doesn't define ssize_t at all */ +typedef SSIZE_T ssize_t; + +#endif + +#endif /* INCLUDE_msvc_compat__ */ diff --git a/src/win32/posix.h b/src/win32/posix.h new file mode 100644 index 000000000..baa4a3b4e --- /dev/null +++ b/src/win32/posix.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_posix__w32_h__ +#define INCLUDE_posix__w32_h__ + +#include "common.h" +#include "compat/fnmatch.h" +#include "utf-conv.h" + +GIT_INLINE(int) p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + +GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret = _wmkdir(buf); + + GIT_UNUSED(mode); + + git__free(buf); + return ret; +} + +extern int p_unlink(const char *path); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_readlink(const char *link, char *target, size_t target_len); +extern int p_hide_directory__w32(const char *path); +extern char *p_realpath(const char *orig_path, char *buffer); +extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); +extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); +extern int p_mkstemp(char *tmp_path); +extern int p_setenv(const char* name, const char* value, int overwrite); +extern int p_stat(const char* path, struct stat* buf); +extern int p_chdir(const char* path); +extern int p_chmod(const char* path, mode_t mode); +extern int p_rmdir(const char* path); +extern int p_access(const char* path, mode_t mode); +extern int p_fsync(int fd); +extern int p_open(const char *path, int flags, ...); +extern int p_creat(const char *path, mode_t mode); +extern int p_getcwd(char *buffer_out, size_t size); +extern int p_rename(const char *from, const char *to); +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); + +#endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c new file mode 100644 index 000000000..10de70da8 --- /dev/null +++ b/src/win32/posix_w32.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "../posix.h" +#include "path.h" +#include "utf-conv.h" +#include +#include +#include + + +int p_unlink(const char *path) +{ + int ret = 0; + wchar_t* buf; + + if ((buf = gitwin_to_utf16(path)) != NULL) { + _wchmod(buf, 0666); + ret = _wunlink(buf); + git__free(buf); + } + + return ret; +} + +int p_fsync(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers(fh)) { + DWORD code = GetLastError(); + + if (code == ERROR_INVALID_HANDLE) + errno = EINVAL; + else + errno = EIO; + + return -1; + } + + return 0; +} + +GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) +{ + long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ + winTime /= 10000000; /* Nano to seconds resolution */ + return (time_t)winTime; +} + +static int do_lstat(const char *file_name, struct stat *buf) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + wchar_t* fbuf = gitwin_to_utf16(file_name); + if (!fbuf) + return -1; + + if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { + int fMode = S_IREAD; + + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + fMode |= S_IFDIR; + else + fMode |= S_IFREG; + + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + fMode |= S_IWRITE; + + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + fMode |= S_IFLNK; + + buf->st_ino = 0; + buf->st_gid = 0; + buf->st_uid = 0; + buf->st_nlink = 1; + buf->st_mode = (mode_t)fMode; + buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow; + buf->st_dev = buf->st_rdev = (_getdrive() - 1); + buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); + buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); + buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + + git__free(fbuf); + return 0; + } + + git__free(fbuf); + return -1; +} + +int p_lstat(const char *file_name, struct stat *buf) +{ + int error; + size_t namelen; + char *alt_name; + + if (do_lstat(file_name, buf) == 0) + return 0; + + /* if file_name ended in a '/', Windows returned ENOENT; + * try again without trailing slashes + */ + namelen = strlen(file_name); + if (namelen && file_name[namelen-1] != '/') + return -1; + + while (namelen && file_name[namelen-1] == '/') + --namelen; + + if (!namelen) + return -1; + + alt_name = git__strndup(file_name, namelen); + if (!alt_name) + return -1; + + error = do_lstat(alt_name, buf); + + git__free(alt_name); + return error; +} + +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; + wchar_t* target_w; + int error = 0; + + assert(link && target && target_len > 0); + + /* + * Try to load the pointer to pGetFinalPath dynamically, because + * it is not available in platforms older than Vista + */ + if (pGetFinalPath == NULL) { + HINSTANCE library = LoadLibrary("kernel32"); + + if (library != NULL) + pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleW"); + + if (pGetFinalPath == NULL) { + giterr_set(GITERR_OS, + "'GetFinalPathNameByHandleW' is not available in this platform"); + return -1; + } + } + + link_w = gitwin_to_utf16(link); + GITERR_CHECK_ALLOC(link_w); + + hFile = CreateFileW(link_w, // file to open + GENERIC_READ, // open for reading + FILE_SHARE_READ, // share for reading + NULL, // default security + OPEN_EXISTING, // existing file only + FILE_FLAG_BACKUP_SEMANTICS, // normal file + NULL); // no attr. template + + git__free(link_w); + + if (hFile == INVALID_HANDLE_VALUE) { + giterr_set(GITERR_OS, "Cannot open '%s' for reading", link); + return -1; + } + + target_w = (wchar_t*)git__malloc(target_len * sizeof(wchar_t)); + GITERR_CHECK_ALLOC(target_w); + + dwRet = pGetFinalPath(hFile, target_w, (DWORD)target_len, 0x0); + if (dwRet == 0 || + dwRet >= target_len || + !WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target, + (int)(target_len * sizeof(char)), NULL, NULL)) + error = -1; + + git__free(target_w); + CloseHandle(hFile); + + if (error) + return error; + + /* Skip first 4 characters if they are "\\?\" */ + if (dwRet > 4 && + target[0] == '\\' && target[1] == '\\' && + target[2] == '?' && target[3] == '\\') + { + unsigned int offset = 4; + dwRet -= 4; + + /* \??\UNC\ */ + if (dwRet > 7 && + target[4] == 'U' && target[5] == 'N' && target[6] == 'C') + { + offset += 2; + dwRet -= 2; + target[offset] = '\\'; + } + + memmove(target, target + offset, dwRet); + } + + target[dwRet] = '\0'; + + return dwRet; +} + +int p_open(const char *path, int flags, ...) +{ + int fd; + wchar_t* buf; + mode_t mode = 0; + + buf = gitwin_to_utf16(path); + if (!buf) + return -1; + + if (flags & O_CREAT) + { + va_list arg_list; + + va_start(arg_list, flags); + mode = va_arg(arg_list, mode_t); + va_end(arg_list); + } + + fd = _wopen(buf, flags | _O_BINARY, mode); + + git__free(buf); + return fd; +} + +int p_creat(const char *path, mode_t mode) +{ + int fd; + wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; + fd = _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); + git__free(buf); + return fd; +} + +int p_getcwd(char *buffer_out, size_t size) +{ + int ret; + wchar_t* buf; + + if ((size_t)((int)size) != size) + return -1; + + buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size); + GITERR_CHECK_ALLOC(buf); + + _wgetcwd(buf, (int)size); + + ret = WideCharToMultiByte( + CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL); + + git__free(buf); + return !ret ? -1 : 0; +} + +int p_stat(const char* path, struct stat* buf) +{ + return do_lstat(path, buf); +} + +int p_chdir(const char* path) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wchdir(buf); + git__free(buf); + return ret; +} + +int p_chmod(const char* path, mode_t mode) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wchmod(buf, mode); + git__free(buf); + return ret; +} + +int p_rmdir(const char* path) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wrmdir(buf); + git__free(buf); + return ret; +} + +int p_hide_directory__w32(const char *path) +{ + int res; + wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; + + res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN); + git__free(buf); + + return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */ +} + +char *p_realpath(const char *orig_path, char *buffer) +{ + int ret, buffer_sz = 0; + wchar_t* orig_path_w = gitwin_to_utf16(orig_path); + wchar_t* buffer_w = (wchar_t*)git__malloc(GIT_PATH_MAX * sizeof(wchar_t)); + + if (!orig_path_w || !buffer_w) + return NULL; + + ret = GetFullPathNameW(orig_path_w, GIT_PATH_MAX, buffer_w, NULL); + git__free(orig_path_w); + + /* According to MSDN, a return value equals to zero means a failure. */ + if (ret == 0 || ret > GIT_PATH_MAX) { + buffer = NULL; + goto done; + } + + if (buffer == NULL) { + buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); + + if (!buffer_sz || + !(buffer = (char *)git__malloc(buffer_sz)) || + !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) + { + git__free(buffer); + buffer = NULL; + goto done; + } + } else { + if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) { + buffer = NULL; + goto done; + } + } + + if (!git_path_exists(buffer)) + { + if (buffer_sz > 0) + git__free(buffer); + + buffer = NULL; + errno = ENOENT; + } + +done: + git__free(buffer_w); + if (buffer) + git_path_mkposix(buffer); + return buffer; +} + +int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ +#ifdef _MSC_VER + int len; + + if (count == 0 || (len = _vsnprintf(buffer, count, format, argptr)) < 0) + return _vscprintf(format, argptr); + + return len; +#else /* MinGW */ + return vsnprintf(buffer, count, format, argptr); +#endif +} + +int p_snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list va; + int r; + + va_start(va, format); + r = p_vsnprintf(buffer, count, format, va); + va_end(va); + + return r; +} + +extern int p_creat(const char *path, mode_t mode); + +int p_mkstemp(char *tmp_path) +{ +#if defined(_MSC_VER) + if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0) + return -1; +#else + if (_mktemp(tmp_path) == NULL) + return -1; +#endif + + return p_creat(tmp_path, 0744); +} + +int p_setenv(const char* name, const char* value, int overwrite) +{ + if (overwrite != 1) + return -1; + + return (SetEnvironmentVariableA(name, value) == 0 ? -1 : 0); +} + +int p_access(const char* path, mode_t mode) +{ + wchar_t *buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + + ret = _waccess(buf, mode); + git__free(buf); + + return ret; +} + +int p_rename(const char *from, const char *to) +{ + wchar_t *wfrom = gitwin_to_utf16(from); + wchar_t *wto = gitwin_to_utf16(to); + int ret; + + if (!wfrom || !wto) + return -1; + + ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; + + git__free(wfrom); + git__free(wto); + + return ret; +} + +int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* giterr_set will be done by caller */ + + return recv(socket, buffer, (int)length, flags); +} + +int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* giterr_set will be done by caller */ + + return send(socket, buffer, (int)length, flags); +} diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 41cf5b35b..3a186c8d9 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -1,85 +1,68 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Original code by Ramiro Polla (Public Domain) + * 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 "pthread.h" -int pthread_create(pthread_t *GIT_RESTRICT thread, - const pthread_attr_t *GIT_RESTRICT GIT_UNUSED(attr), - void *(*start_routine)(void*), void *GIT_RESTRICT arg) +int pthread_create( + pthread_t *GIT_RESTRICT thread, + const pthread_attr_t *GIT_RESTRICT attr, + void *(*start_routine)(void*), + void *GIT_RESTRICT arg) { - GIT_UNUSED_ARG(attr); - *thread = (pthread_t) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); - return *thread ? GIT_SUCCESS : git__throw(GIT_EOSERR, "Failed to create pthread"); + GIT_UNUSED(attr); + *thread = (pthread_t) CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); + return *thread ? 0 : -1; } int pthread_join(pthread_t thread, void **value_ptr) { - int ret; - ret = WaitForSingleObject(thread, INFINITE); - if (ret && value_ptr) - GetExitCodeThread(thread, (void*) value_ptr); - return -(!!ret); + int ret; + ret = WaitForSingleObject(thread, INFINITE); + if (ret && value_ptr) + GetExitCodeThread(thread, (void*) value_ptr); + return -(!!ret); } int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, - const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) + const pthread_mutexattr_t *GIT_RESTRICT mutexattr) { - GIT_UNUSED_ARG(mutexattr); - InitializeCriticalSection(mutex); - return 0; + GIT_UNUSED(mutexattr); + InitializeCriticalSection(mutex); + return 0; } int pthread_mutex_destroy(pthread_mutex_t *mutex) { - DeleteCriticalSection(mutex); - return 0; + DeleteCriticalSection(mutex); + return 0; } int pthread_mutex_lock(pthread_mutex_t *mutex) { - EnterCriticalSection(mutex); - return 0; + EnterCriticalSection(mutex); + return 0; } int pthread_mutex_unlock(pthread_mutex_t *mutex) { - LeaveCriticalSection(mutex); - return 0; + LeaveCriticalSection(mutex); + return 0; } int pthread_num_processors_np(void) { - DWORD_PTR p, s; - int n = 0; + DWORD_PTR p, s; + int n = 0; - if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s)) - for (; p; p >>= 1) - n += p&1; + if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s)) + for (; p; p >>= 1) + n += p&1; - return n ? n : 1; + return n ? n : 1; } diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 10949f1eb..b194cbfa7 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -1,28 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Original code by Ramiro Polla (Public Domain) + * 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 GIT_PTHREAD_H @@ -45,8 +25,8 @@ typedef HANDLE pthread_t; #define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; int pthread_create(pthread_t *GIT_RESTRICT, - const pthread_attr_t *GIT_RESTRICT, - void *(*start_routine)(void*), void *__restrict); + const pthread_attr_t *GIT_RESTRICT, + void *(*start_routine)(void*), void *__restrict); int pthread_join(pthread_t, void **); diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c new file mode 100644 index 000000000..0a705c0ad --- /dev/null +++ b/src/win32/utf-conv.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "utf-conv.h" +#include "git2/windows.h" + +/* + * Default codepage value + */ +static int _active_codepage = CP_UTF8; + +void gitwin_set_codepage(unsigned int codepage) +{ + _active_codepage = codepage; +} + +unsigned int gitwin_get_codepage(void) +{ + return _active_codepage; +} + +void gitwin_set_utf8(void) +{ + _active_codepage = CP_UTF8; +} + +wchar_t* gitwin_to_utf16(const char* str) +{ + wchar_t* ret; + int cb; + + if (!str) + return NULL; + + cb = MultiByteToWideChar(_active_codepage, 0, str, -1, NULL, 0); + if (cb == 0) + return (wchar_t *)git__calloc(1, sizeof(wchar_t)); + + ret = (wchar_t *)git__malloc(cb * sizeof(wchar_t)); + if (!ret) + return NULL; + + if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, (int)cb) == 0) { + giterr_set(GITERR_OS, "Could not convert string to UTF-16"); + git__free(ret); + ret = NULL; + } + + return ret; +} + +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len) +{ + int result = MultiByteToWideChar( + _active_codepage, 0, str, -1, buffer, (int)len); + if (result == 0) + giterr_set(GITERR_OS, "Could not convert string to UTF-16"); + return result; +} + +char* gitwin_from_utf16(const wchar_t* str) +{ + char* ret; + int cb; + + if (!str) + return NULL; + + cb = WideCharToMultiByte(_active_codepage, 0, str, -1, NULL, 0, NULL, NULL); + if (cb == 0) + return (char *)git__calloc(1, sizeof(char)); + + ret = (char*)git__malloc(cb); + if (!ret) + return NULL; + + if (WideCharToMultiByte( + _active_codepage, 0, str, -1, ret, (int)cb, NULL, NULL) == 0) + { + giterr_set(GITERR_OS, "Could not convert string to UTF-8"); + git__free(ret); + ret = NULL; + } + + return ret; + +} diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h new file mode 100644 index 000000000..ae9f29f6c --- /dev/null +++ b/src/win32/utf-conv.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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__ + +wchar_t* gitwin_to_utf16(const char* str); +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len); +char* gitwin_from_utf16(const wchar_t* str); + +#endif + diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h new file mode 100644 index 000000000..cb8b235b5 --- /dev/null +++ b/src/xdiff/xdiff.h @@ -0,0 +1,135 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XDIFF_H) +#define XDIFF_H + +#ifdef __cplusplus +extern "C" { +#endif /* #ifdef __cplusplus */ + + +#define XDF_NEED_MINIMAL (1 << 1) +#define XDF_IGNORE_WHITESPACE (1 << 2) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) +#define XDF_PATIENCE_DIFF (1 << 5) +#define XDF_HISTOGRAM_DIFF (1 << 6) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) + +#define XDL_PATCH_NORMAL '-' +#define XDL_PATCH_REVERSE '+' +#define XDL_PATCH_MODEMASK ((1 << 8) - 1) +#define XDL_PATCH_IGNOREBSPACE (1 << 8) + +#define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_COMMON (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) + +#define XDL_MMB_READONLY (1 << 0) + +#define XDL_MMF_ATOMIC (1 << 0) + +#define XDL_BDOP_INS 1 +#define XDL_BDOP_CPY 2 +#define XDL_BDOP_INSB 3 + +/* merge simplification levels */ +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 + +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR_UNION 3 + +/* merge output styles */ +#define XDL_MERGE_DIFF3 1 + +typedef struct s_mmfile { + char *ptr; + size_t size; +} mmfile_t; + +typedef struct s_mmbuffer { + char *ptr; + size_t size; +} mmbuffer_t; + +typedef struct s_xpparam { + unsigned long flags; +} xpparam_t; + +typedef struct s_xdemitcb { + void *priv; + int (*outf)(void *, mmbuffer_t *, int); +} xdemitcb_t; + +typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); + +typedef struct s_xdemitconf { + long ctxlen; + long interhunkctxlen; + unsigned long flags; + find_func_t find_func; + void *find_func_priv; + void (*emit_func)(void); +} xdemitconf_t; + +typedef struct s_bdiffparam { + long bsize; +} bdiffparam_t; + + +#define xdl_malloc(x) malloc(x) +#define xdl_free(ptr) free(ptr) +#define xdl_realloc(ptr,x) realloc(ptr,x) + +void *xdl_mmfile_first(mmfile_t *mmf, long *size); +long xdl_mmfile_size(mmfile_t *mmf); + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb); + +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif /* #if !defined(XDIFF_H) */ diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c new file mode 100644 index 000000000..75a392275 --- /dev/null +++ b/src/xdiff/xdiffi.c @@ -0,0 +1,572 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 + + + +typedef struct s_xdpsplit { + long i1, i2; + int min_lo, min_hi; +} xdpsplit_t; + + + + +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv); +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); + + + + + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might end up having to expensive + * cases using this algorithm is full, so a little bit of heuristic is needed + * to cut the search and to return a suboptimal point. + */ +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv) { + long dmin = off1 - lim2, dmax = lim1 - off2; + long fmid = off1 - off2, bmid = lim1 - lim2; + long odd = (fmid - bmid) & 1; + long fmin = fmid, fmax = fmid; + long bmin = bmid, bmax = bmid; + long ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) { + int got_snake = 0; + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); + if (i1 - prev1 > xenv->snake_cnt) + got_snake = 1; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); + if (prev1 - i1 > xenv->snake_cnt) + got_snake = 1; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of the, have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > xenv->heur_min) { + for (best = 0, d = fmax; d >= fmin; d -= 2) { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + xenv->snake_cnt <= i1 && i1 < lim1 && + off2 + xenv->snake_cnt <= i2 && i2 < lim2) { + for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) + if (k == xenv->snake_cnt) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 1; + spl->min_hi = 0; + return ec; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - xenv->snake_cnt && + off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { + for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) + if (k == xenv->snake_cnt - 1) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 0; + spl->min_hi = 1; + return ec; + } + } + + /* + * Enough is enough. We spent too much time here and now we collect + * the furthest reaching path using the (i1 + i2) measure. + */ + if (ec >= xenv->mxcost) { + long fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) { + i1 = XDL_MIN(kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) { + i1 = XDL_MAX(off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } else { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + return ec; + } + } +} + + +/* + * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling + * the box splitting function. Note that the real job (marking changed lines) + * is done in the two boundary reaching checks. + */ +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { + unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; + + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); + for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) { + char *rchg2 = dd2->rchg; + long *rindex2 = dd2->rindex; + + for (; off2 < lim2; off2++) + rchg2[rindex2[off2]] = 1; + } else if (off2 == lim2) { + char *rchg1 = dd1->rchg; + long *rindex1 = dd1->rindex; + + for (; off1 < lim1; off1++) + rchg1[rindex1[off1]] = 1; + } else { + xdpsplit_t spl; + spl.i1 = spl.i2 = 0; + + /* + * Divide ... + */ + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { + + return -1; + } + + /* + * ... et Impera. + */ + if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, xenv) < 0 || + xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, xenv) < 0) { + + return -1; + } + } + + return 0; +} + + +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long ndiags; + long *kvd, *kvdf, *kvdb; + xdalgoenv_t xenv; + diffdata_t dd1, dd2; + + if (xpp->flags & XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + return xdl_do_histogram_diff(mf1, mf2, xpp, xe); + + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { + + return -1; + } + + /* + * Allocate and setup K vectors to be used by the differential algorithm. + * One is to store the forward path and one to store the backward path. + */ + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; + + if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, + kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { + + xdl_free(kvd); + xdl_free_env(xe); + return -1; + } + + xdl_free(kvd); + + return 0; +} + + +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { + xdchange_t *xch; + + if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) + return NULL; + + xch->next = xscr; + xch->i1 = i1; + xch->i2 = i2; + xch->chg1 = chg1; + xch->chg2 = chg2; + + return xch; +} + + +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; + char *rchg = xdf->rchg, *rchgo = xdfo->rchg; + xrecord_t **recs = xdf->recs; + + /* + * This is the same of what GNU diff does. Move back and forward + * change groups for a consistent and pretty diff output. This also + * helps in finding joinable change groups and reduce the diff size. + */ + for (ix = ixo = 0;;) { + /* + * Find the first changed line in the to-be-compacted file. + * We need to keep track of both indexes, so if we find a + * changed lines group on the other file, while scanning the + * to-be-compacted file, we need to skip it properly. Note + * that loops that are testing for changed lines on rchg* do + * not need index bounding since the array is prepared with + * a zero at position -1 and N. + */ + for (; ix < nrec && !rchg[ix]; ix++) + while (rchgo[ixo++]); + if (ix == nrec) + break; + + /* + * Record the start of a changed-group in the to-be-compacted file + * and find the end of it, on both to-be-compacted and other file + * indexes (ix and ixo). + */ + ixs = ix; + for (ix++; rchg[ix]; ix++); + for (; rchgo[ixo]; ixo++); + + do { + grpsiz = ix - ixs; + + /* + * If the line before the current change group, is equal to + * the last line of the current change group, shift backward + * the group. + */ + while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha && + xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). + */ + for (; rchg[ixs - 1]; ixs--); + while (rchgo[--ixo]); + } + + /* + * Record the end-of-group position in case we are matched + * with a group of changes in the other file (that is, the + * change record before the end-of-group index in the other + * file is set). + */ + ixref = rchgo[ixo - 1] ? ix: nrec; + + /* + * If the first line of the current change group, is equal to + * the line next of the current change group, shift forward + * the group. + */ + while (ix < nrec && recs[ixs]->ha == recs[ix]->ha && + xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) { + rchg[ixs++] = 0; + rchg[ix++] = 1; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). Keep tracking the reference + * index in case we are shifting together with a + * corresponding group of changes in the other file. + */ + for (; rchg[ix]; ix++); + while (rchgo[++ixo]) + ixref = ix; + } + } while (grpsiz != ix - ixs); + + /* + * Try to move back the possibly merged group of changes, to match + * the recorded postion in the other file. + */ + while (ixref < ix) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + while (rchgo[--ixo]); + } + } + + return 0; +} + + +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { + xdchange_t *cscr = NULL, *xch; + char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; + long i1, i2, l1, l2; + + /* + * Trivial. Collects "groups" of changes and creates an edit script. + */ + for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) + if (rchg1[i1 - 1] || rchg2[i2 - 1]) { + for (l1 = i1; rchg1[i1 - 1]; i1--); + for (l2 = i2; rchg2[i2 - 1]; i2--); + + if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { + xdl_free_script(cscr); + return -1; + } + cscr = xch; + } + + *xscr = cscr; + + return 0; +} + + +void xdl_free_script(xdchange_t *xscr) { + xdchange_t *xch; + + while ((xch = xscr) != NULL) { + xscr = xscr->next; + xdl_free(xch); + } +} + + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb) { + xdchange_t *xscr; + xdfenv_t xe; + emit_func_t ef = xecfg->emit_func ? + (emit_func_t)xecfg->emit_func : xdl_emit_diff; + + if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { + + return -1; + } + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + + xdl_free_env(&xe); + return -1; + } + if (xscr) { + if (ef(&xe, xscr, ecb, xecfg) < 0) { + + xdl_free_script(xscr); + xdl_free_env(&xe); + return -1; + } + xdl_free_script(xscr); + } + xdl_free_env(&xe); + + return 0; +} diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h new file mode 100644 index 000000000..7a92ea9c4 --- /dev/null +++ b/src/xdiff/xdiffi.h @@ -0,0 +1,63 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XDIFFI_H) +#define XDIFFI_H + + +typedef struct s_diffdata { + long nrec; + unsigned long const *ha; + long *rindex; + char *rchg; +} diffdata_t; + +typedef struct s_xdalgoenv { + long mxcost; + long snake_cnt; + long heur_min; +} xdalgoenv_t; + +typedef struct s_xdchange { + struct s_xdchange *next; + long i1, i2; + long chg1, chg2; +} xdchange_t; + + + +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); +void xdl_free_script(xdchange_t *xscr); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); +int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); + +#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c new file mode 100644 index 000000000..e3e63d902 --- /dev/null +++ b/src/xdiff/xemit.c @@ -0,0 +1,253 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + + + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec); +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb); + + + + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { + + *rec = xdf->recs[ri]->ptr; + + return xdf->recs[ri]->size; +} + + +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { + long size, psize = (long)strlen(pre); + char const *rec; + + size = xdl_get_rec(xdf, ri, &rec); + if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { + + return -1; + } + + return 0; +} + + +/* + * Starting at the passed change atom, find the latest change atom to be included + * inside the differential hunk according to the specified configuration. + */ +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { + xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + + for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) + break; + + return xchp; +} + + +static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) +{ + (void)priv; + + if (len > 0 && + (isalpha((unsigned char)*rec) || /* identifier? */ + *rec == '_' || /* also identifier? */ + *rec == '$')) { /* identifiers from VMS and other esoterico */ + if (len > sz) + len = sz; + while (0 < len && isspace((unsigned char)rec[len - 1])) + len--; + memcpy(buf, rec, len); + return len; + } + return -1; +} + +static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + xdfile_t *xdf = &xe->xdf2; + const char *rchg = xdf->rchg; + long ix; + + (void)xscr; + (void)xecfg; + + for (ix = 0; ix < xdf->nrec; ix++) { + if (rchg[ix]) + continue; + if (xdl_emit_record(xdf, ix, "", ecb)) + return -1; + } + return 0; +} + +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + const char *rec; + long reclen = xdl_get_rec(&xe->xdf1, l, &rec); + long len = ff(rec, reclen, buf, size, xecfg->find_func_priv); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + long s1, s2, e1, e2, lctx; + xdchange_t *xch, *xche; + long funclineprev = -1; + struct func_line func_line = { 0 }; + + if (xecfg->flags & XDL_EMIT_COMMON) + return xdl_emit_common(xe, xscr, ecb, xecfg); + + for (xch = xscr; xch; xch = xche->next) { + xche = xdl_get_hunk(xch, xecfg); + + s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); + s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1); + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 -= s1 - fs1; + s1 = fs1; + } + } + + again: + lctx = xecfg->ctxlen; + lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + + e1 = xche->i1 + xche->chg1 + lctx; + e2 = xche->i2 + xche->chg2 + lctx; + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 += fe1 - e1; + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = xche->next->i1; + if (l <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto again; + } + } + } + + /* + * Emit current hunk header. + */ + + if (xecfg->flags & XDL_EMIT_FUNCNAMES) { + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); + funclineprev = s1 - 1; + } + if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + func_line.buf, func_line.len, ecb) < 0) + return -1; + + /* + * Emit pre-context. + */ + for (; s2 < xch->i2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { + /* + * Merge previous with current change atom. + */ + for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + /* + * Removes lines from the first file. + */ + for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) + if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) + return -1; + + /* + * Adds lines from the second file. + */ + for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) + return -1; + + if (xch == xche) + break; + s1 = xch->i1 + xch->chg1; + s2 = xch->i2 + xch->chg2; + } + + /* + * Emit post-context. + */ + for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + } + + return 0; +} diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h new file mode 100644 index 000000000..c2e2e8302 --- /dev/null +++ b/src/xdiff/xemit.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XEMIT_H) +#define XEMIT_H + + +typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + + + +#endif /* #if !defined(XEMIT_H) */ diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c new file mode 100644 index 000000000..5d101754d --- /dev/null +++ b/src/xdiff/xhistogram.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in JGit's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - 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. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR + * 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. + */ + +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +#define MAX_PTR UINT_MAX +#define MAX_CNT UINT_MAX + +#define LINE_END(n) (line##n + count##n - 1) +#define LINE_END_PTR(n) (*line##n + *count##n - 1) + +struct histindex { + struct record { + unsigned int ptr, cnt; + struct record *next; + } **records, /* an ocurrence */ + **line_map; /* map of line to record chain */ + chastore_t rcha; + unsigned int *next_ptrs; + unsigned int table_bits, + records_size, + line_map_size; + + unsigned int max_chain_length, + key_shift, + ptr_shift; + + unsigned int cnt, + has_common; + + xdfenv_t *env; + xpparam_t const *xpp; +}; + +struct region { + unsigned int begin1, end1; + unsigned int begin2, end2; +}; + +#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) + +#define NEXT_PTR(index, ptr) \ + (index->next_ptrs[(ptr) - index->ptr_shift]) + +#define CNT(index, ptr) \ + ((LINE_MAP(index, ptr))->cnt) + +#define REC(env, s, l) \ + (env->xdf##s.recs[l - 1]) + +static int cmp_recs(xpparam_t const *xpp, + xrecord_t *r1, xrecord_t *r2) +{ + return r1->ha == r2->ha && + xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size, + xpp->flags); +} + +#define CMP_ENV(xpp, env, s1, l1, s2, l2) \ + (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2))) + +#define CMP(i, s1, l1, s2, l2) \ + (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2))) + +#define TABLE_HASH(index, side, line) \ + XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + +static int scanA(struct histindex *index, unsigned int line1, unsigned int count1) +{ + unsigned int ptr; + unsigned int tbl_idx; + unsigned int chain_len; + struct record **rec_chain, *rec; + + for (ptr = LINE_END(1); line1 <= ptr; ptr--) { + tbl_idx = TABLE_HASH(index, 1, ptr); + rec_chain = index->records + tbl_idx; + rec = *rec_chain; + + chain_len = 0; + while (rec) { + if (CMP(index, 1, rec->ptr, 1, ptr)) { + /* + * ptr is identical to another element. Insert + * it onto the front of the existing element + * chain. + */ + NEXT_PTR(index, ptr) = rec->ptr; + rec->ptr = ptr; + /* cap rec->cnt at MAX_CNT */ + rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); + LINE_MAP(index, ptr) = rec; + goto continue_scan; + } + + rec = rec->next; + chain_len++; + } + + if (chain_len == index->max_chain_length) + return -1; + + /* + * This is the first time we have ever seen this particular + * element in the sequence. Construct a new chain for it. + */ + if (!(rec = xdl_cha_alloc(&index->rcha))) + return -1; + rec->ptr = ptr; + rec->cnt = 1; + rec->next = *rec_chain; + *rec_chain = rec; + LINE_MAP(index, ptr) = rec; + +continue_scan: + ; /* no op */ + } + + return 0; +} + +static int try_lcs( + struct histindex *index, struct region *lcs, unsigned int b_ptr, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_next = b_ptr + 1; + struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; + unsigned int as, ae, bs, be, np, rc; + int should_break; + + for (; rec; rec = rec->next) { + if (rec->cnt > index->cnt) { + if (!index->has_common) + index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); + continue; + } + + as = rec->ptr; + if (!CMP(index, 1, as, 2, b_ptr)) + continue; + + index->has_common = 1; + for (;;) { + should_break = 0; + np = NEXT_PTR(index, as); + bs = b_ptr; + ae = as; + be = bs; + rc = rec->cnt; + + while (line1 < as && line2 < bs + && CMP(index, 1, as - 1, 2, bs - 1)) { + as--; + bs--; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, as)); + } + while (ae < LINE_END(1) && be < LINE_END(2) + && CMP(index, 1, ae + 1, 2, be + 1)) { + ae++; + be++; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, ae)); + } + + if (b_next <= be) + b_next = be + 1; + if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { + lcs->begin1 = as; + lcs->begin2 = bs; + lcs->end1 = ae; + lcs->end2 = be; + index->cnt = rc; + } + + if (np == 0) + break; + + while (np <= ae) { + np = NEXT_PTR(index, np); + if (np == 0) { + should_break = 1; + break; + } + } + + if (should_break) + break; + + as = np; + } + } + return b_next; +} + +static int find_lcs( + struct histindex *index, struct region *lcs, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_ptr; + + if (scanA(index, line1, count1)) + return -1; + + index->cnt = index->max_chain_length + 1; + + for (b_ptr = line2; b_ptr <= LINE_END(2); ) + b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2); + + return index->has_common && index->max_chain_length < index->cnt; +} + +static int fall_back_to_classic_diff(struct histindex *index, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF; + + return xdl_fall_back_diff(index->env, &xpp, + line1, count1, line2, count2); +} + +static int histogram_diff( + xpparam_t const *xpp, xdfenv_t *env, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + struct histindex index; + struct region lcs; + unsigned int sz; + int result = -1; + + if (count1 <= 0 && count2 <= 0) + return 0; + + if (LINE_END(1) >= MAX_PTR) + return -1; + + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&index, 0, sizeof(index)); + + index.env = env; + index.xpp = xpp; + + index.records = NULL; + index.line_map = NULL; + /* in case of early xdl_cha_free() */ + index.rcha.head = NULL; + + index.table_bits = xdl_hashbits(count1); + sz = index.records_size = 1 << index.table_bits; + sz *= sizeof(struct record *); + if (!(index.records = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.records, 0, sz); + + sz = index.line_map_size = count1; + sz *= sizeof(struct record *); + if (!(index.line_map = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.line_map, 0, sz); + + sz = index.line_map_size; + sz *= sizeof(unsigned int); + if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) + goto cleanup; + memset(index.next_ptrs, 0, sz); + + /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) + goto cleanup; + + index.ptr_shift = line1; + index.max_chain_length = 64; + + memset(&lcs, 0, sizeof(lcs)); + if (find_lcs(&index, &lcs, line1, count1, line2, count2)) + result = fall_back_to_classic_diff(&index, line1, count1, line2, count2); + else { + if (lcs.begin1 == 0 && lcs.begin2 == 0) { + while (count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while (count2--) + env->xdf2.rchg[line2++ - 1] = 1; + result = 0; + } else { + result = histogram_diff(xpp, env, + line1, lcs.begin1 - line1, + line2, lcs.begin2 - line2); + if (result) + goto cleanup; + result = histogram_diff(xpp, env, + lcs.end1 + 1, LINE_END(1) - lcs.end1, + lcs.end2 + 1, LINE_END(2) - lcs.end2); + if (result) + goto cleanup; + } + } + +cleanup: + xdl_free(index.records); + xdl_free(index.line_map); + xdl_free(index.next_ptrs); + xdl_cha_free(&index.rcha); + + return result; +} + +int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + return histogram_diff(xpp, env, + env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, + env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); +} diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h new file mode 100644 index 000000000..4a1cde909 --- /dev/null +++ b/src/xdiff/xinclude.h @@ -0,0 +1,46 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XINCLUDE_H) +#define XINCLUDE_H + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#else +#include +#endif + +#include "xmacros.h" +#include "xdiff.h" +#include "xtypes.h" +#include "xutils.h" +#include "xprepare.h" +#include "xdiffi.h" +#include "xemit.h" + + +#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h new file mode 100644 index 000000000..165a895a9 --- /dev/null +++ b/src/xdiff/xmacros.h @@ -0,0 +1,54 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XMACROS_H) +#define XMACROS_H + + + + +#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) +#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) +#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) +#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) +#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) +#define XDL_LE32_PUT(p, v) \ +do { \ + unsigned char *__p = (unsigned char *) (p); \ + *__p++ = (unsigned char) (v); \ + *__p++ = (unsigned char) ((v) >> 8); \ + *__p++ = (unsigned char) ((v) >> 16); \ + *__p = (unsigned char) ((v) >> 24); \ +} while (0) +#define XDL_LE32_GET(p, v) \ +do { \ + unsigned char const *__p = (unsigned char const *) (p); \ + (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ + ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ +} while (0) + + +#endif /* #if !defined(XMACROS_H) */ diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c new file mode 100644 index 000000000..84e424672 --- /dev/null +++ b/src/xdiff/xmerge.c @@ -0,0 +1,619 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + * 3 = no conflict, take both. + */ + int mode; + /* + * These point at the respective postimages. E.g. is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ + long i1, i2; + long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs; + int size = 0; + + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *name3, + int size, int i, int style, + xdmerge_t *m, char *dest, int marker_size) +{ + int marker1_size = (name1 ? (int)strlen(name1) + 1 : 0); + int marker2_size = (name2 ? (int)strlen(name2) + 1 : 0); + int marker3_size = (name3 ? (int)strlen(name3) + 1 : 0); + + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + marker1_size; + } else { + memset(dest + size, '<', marker_size); + size += marker_size; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1 + marker3_size; + } else { + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1; + } else { + memset(dest + size, '=', marker_size); + size += marker_size; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + marker2_size; + } else { + memset(dest + size, '>', marker_size); + size += marker_size; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *ancestor_name, + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, + size, i, style, m, dest, + marker_size); + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + /* Postimage from side #1 */ + if (m->mode & 1) + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + } else + continue; + i = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, the changes are identical. */ + xdl_free_env(&xe); + m->mode = 4; + continue; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum((unsigned char)*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; + int i0, i1, i2, chg0, chg1, chg2; + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; + + if (style == XDL_MERGE_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) { + i0 -= off; + i1 -= off; + } + else + i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo < 0) { + chg0 -= ffo; + chg1 -= ffo; + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i0 = xscr2->i1; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (XDL_MERGE_ZEALOUS <= level && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int marker_size = xmp->marker_size; + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, + favor, changes, NULL, style, + marker_size); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, + result->ptr, style, marker_size); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + xpparam_t const *xpp = &xmp->xpp; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c new file mode 100644 index 000000000..fdd7d0263 --- /dev/null +++ b/src/xdiff/xpatience.c @@ -0,0 +1,358 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(int line, struct hashmap *map, int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1], *other; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + other = map->env->xdf1.recs[map->entries[index].line1 - 1]; + if (map->entries[index].hash != record->ha || + !xdl_recmatch(record->ptr, record->size, + other->ptr, other->size, + map->xpp->flags)) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = (left + right) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + sequence[++i] = entry; + if (i == longest) + longest++; + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return xdl_recmatch(record1->ptr, record1->size, + record2->ptr, record2->size, map->xpp->flags); +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + struct hashmap submap; + + memset(&submap, 0, sizeof(submap)); + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF; + + return xdl_fall_back_diff(map->env, &xpp, + line1, count1, line2, count2); +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c new file mode 100644 index 000000000..e419f4f72 --- /dev/null +++ b/src/xdiff/xprepare.c @@ -0,0 +1,483 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + +#define XDL_KPDIS_RUN 4 +#define XDL_MAX_EQLIMIT 1024 +#define XDL_SIMSCAN_WINDOW 100 +#define XDL_GUESS_NLINES1 256 +#define XDL_GUESS_NLINES2 20 + + +typedef struct s_xdlclass { + struct s_xdlclass *next; + unsigned long ha; + char const *line; + long size; + long idx; + long len1, len2; +} xdlclass_t; + +typedef struct s_xdlclassifier { + unsigned int hbits; + long hsize; + xdlclass_t **rchash; + chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; + long count; + long flags; +} xdlclassifier_t; + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); +static void xdl_free_classifier(xdlclassifier_t *cf); +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf); +static void xdl_free_ctx(xdfile_t *xdf); +static int xdl_clean_mmatch(char const *dis, long i, long s, long e); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { + cf->flags = flags; + + cf->hbits = xdl_hashbits((unsigned int) size); + cf->hsize = 1 << cf->hbits; + + if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { + + return -1; + } + if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { + + xdl_cha_free(&cf->ncha); + return -1; + } + memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + + cf->count = 0; + + return 0; +} + + +static void xdl_free_classifier(xdlclassifier_t *cf) { + + xdl_free(cf->rcrecs); + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); +} + + +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { + long hi; + char const *line; + xdlclass_t *rcrec; + xdlclass_t **rcrecs; + + line = rec->ptr; + hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) + if (rcrec->ha == rec->ha && + xdl_recmatch(rcrec->line, rcrec->size, + rec->ptr, rec->size, cf->flags)) + break; + + if (!rcrec) { + if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { + + return -1; + } + rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; + rcrec->line = line; + rcrec->size = rec->size; + rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; + rcrec->next = cf->rchash[hi]; + cf->rchash[hi] = rcrec; + } + + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + + rec->ha = (unsigned long) rcrec->idx; + + hi = (long) XDL_HASHLONG(rec->ha, hbits); + rec->next = rhash[hi]; + rhash[hi] = rec; + + return 0; +} + + +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf) { + unsigned int hbits; + long nrec, hsize, bsize; + unsigned long hav; + char const *blk, *cur, *top, *prev; + xrecord_t *crec; + xrecord_t **recs, **rrecs; + xrecord_t **rhash; + unsigned long *ha; + char *rchg; + long *rindex; + + ha = NULL; + rindex = NULL; + rchg = NULL; + rhash = NULL; + recs = NULL; + + if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) + goto abort; + if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) + goto abort; + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + hbits = hsize = 0; + else { + hbits = xdl_hashbits((unsigned int) narec); + hsize = 1 << hbits; + if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) + goto abort; + memset(rhash, 0, hsize * sizeof(xrecord_t *)); + } + + nrec = 0; + if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { + for (top = blk + bsize; cur < top; ) { + prev = cur; + hav = xdl_hash_record(&cur, top, xpp->flags); + if (nrec >= narec) { + narec *= 2; + if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) + goto abort; + recs = rrecs; + } + if (!(crec = xdl_cha_alloc(&xdf->rcha))) + goto abort; + crec->ptr = prev; + crec->size = (long) (cur - prev); + crec->ha = hav; + recs[nrec++] = crec; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) + goto abort; + } + } + + if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) + goto abort; + memset(rchg, 0, (nrec + 2) * sizeof(char)); + + if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) + goto abort; + if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) + goto abort; + + xdf->nrec = nrec; + xdf->recs = recs; + xdf->hbits = hbits; + xdf->rhash = rhash; + xdf->rchg = rchg + 1; + xdf->rindex = rindex; + xdf->nreff = 0; + xdf->ha = ha; + xdf->dstart = 0; + xdf->dend = nrec - 1; + + return 0; + +abort: + xdl_free(ha); + xdl_free(rindex); + xdl_free(rchg); + xdl_free(rhash); + xdl_free(recs); + xdl_cha_free(&xdf->rcha); + return -1; +} + + +static void xdl_free_ctx(xdfile_t *xdf) { + + xdl_free(xdf->rhash); + xdl_free(xdf->rindex); + xdl_free(xdf->rchg - 1); + xdl_free(xdf->ha); + xdl_free(xdf->recs); + xdl_cha_free(&xdf->rcha); +} + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long enl1, enl2, sample; + xdlclassifier_t cf; + + memset(&cf, 0, sizeof(cf)); + + /* + * For histogram diff, we can afford a smaller sample size and + * thus a poorer estimate of the number of lines, as the hash + * table (rhash) won't be filled up/grown. The number of lines + * (nrecs) will be updated correctly anyway by + * xdl_prepare_ctx(). + */ + sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1; + + enl1 = xdl_guess_lines(mf1, sample) + 1; + enl2 = xdl_guess_lines(mf2, sample) + 1; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) { + + return -1; + } + + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + + xdl_free_classifier(&cf); + return -1; + } + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + if (!(xpp->flags & XDF_PATIENCE_DIFF) && + !(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); + return -1; + } + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF)) + xdl_free_classifier(&cf); + + return 0; +} + + +void xdl_free_env(xdfenv_t *xe) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); +} + + +static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { + long r, rdis0, rpdis0, rdis1, rpdis1; + + /* + * Limits the window the is examined during the similar-lines + * scan. The loops below stops when dis[i - r] == 1 (line that + * has no match), but there are corner cases where the loop + * proceed all the way to the extremities by causing huge + * performance penalties in case of big files. + */ + if (i - s > XDL_SIMSCAN_WINDOW) + s = i - XDL_SIMSCAN_WINDOW; + if (e - i > XDL_SIMSCAN_WINDOW) + e = i + XDL_SIMSCAN_WINDOW; + + /* + * Scans the lines before 'i' to find a run of lines that either + * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). + * Note that we always call this function with dis[i] > 1, so the + * current line (i) is already a multimatch line. + */ + for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { + if (!dis[i - r]) + rdis0++; + else if (dis[i - r] == 2) + rpdis0++; + else + break; + } + /* + * If the run before the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + * We want to discard multimatch lines only when they appear in the + * middle of runs with nomatch lines (dis[j] == 0). + */ + if (rdis0 == 0) + return 0; + for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { + if (!dis[i + r]) + rdis1++; + else if (dis[i + r] == 2) + rpdis1++; + else + break; + } + /* + * If the run after the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + */ + if (rdis1 == 0) + return 0; + rdis1 += rdis0; + rpdis1 += rpdis0; + + return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); +} + + +/* + * Try to reduce the problem complexity, discard records that have no + * matches on the other file. Also, lines that have multiple matches + * might be potentially discarded if they happear in a run of discardable. + */ +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff, mlim; + xrecord_t **recs; + xdlclass_t *rcrec; + char *dis, *dis1, *dis2; + + if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { + + return -1; + } + memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); + dis1 = dis; + dis2 = dis1 + xdf1->nrec + 1; + + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + i <= xdf1->dend; i++, recs++) { + if (dis1[i] == 1 || + (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { + xdf1->rindex[nreff] = i; + xdf1->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf1->rchg[i] = 1; + } + xdf1->nreff = nreff; + + for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + i <= xdf2->dend; i++, recs++) { + if (dis2[i] == 1 || + (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { + xdf2->rindex[nreff] = i; + xdf2->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf2->rchg[i] = 1; + } + xdf2->nreff = nreff; + + xdl_free(dis); + + return 0; +} + + +/* + * Early trim initial and terminal matching records. + */ +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { + long i, lim; + xrecord_t **recs1, **recs2; + + recs1 = xdf1->recs; + recs2 = xdf2->recs; + for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + i++, recs1++, recs2++) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dstart = xdf2->dstart = i; + + recs1 = xdf1->recs + xdf1->nrec - 1; + recs2 = xdf2->recs + xdf2->nrec - 1; + for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dend = xdf1->nrec - i - 1; + xdf2->dend = xdf2->nrec - i - 1; + + return 0; +} + + +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + + if (xdl_trim_ends(xdf1, xdf2) < 0 || + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { + + return -1; + } + + return 0; +} diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h new file mode 100644 index 000000000..8fb06a537 --- /dev/null +++ b/src/xdiff/xprepare.h @@ -0,0 +1,34 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XPREPARE_H) +#define XPREPARE_H + + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +void xdl_free_env(xdfenv_t *xe); + + + +#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h new file mode 100644 index 000000000..2511aef8d --- /dev/null +++ b/src/xdiff/xtypes.h @@ -0,0 +1,67 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XTYPES_H) +#define XTYPES_H + + + +typedef struct s_chanode { + struct s_chanode *next; + long icurr; +} chanode_t; + +typedef struct s_chastore { + chanode_t *head, *tail; + long isize, nsize; + chanode_t *ancur; + chanode_t *sncur; + long scurr; +} chastore_t; + +typedef struct s_xrecord { + struct s_xrecord *next; + char const *ptr; + long size; + unsigned long ha; +} xrecord_t; + +typedef struct s_xdfile { + chastore_t rcha; + long nrec; + unsigned int hbits; + xrecord_t **rhash; + long dstart, dend; + xrecord_t **recs; + char *rchg; + long *rindex; + long nreff; + unsigned long *ha; +} xdfile_t; + +typedef struct s_xdfenv { + xdfile_t xdf1, xdf2; +} xdfenv_t; + + + +#endif /* #if !defined(XTYPES_H) */ diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c new file mode 100644 index 000000000..bb7bdee49 --- /dev/null +++ b/src/xdiff/xutils.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + + + +long xdl_bogosqrt(long n) { + long i; + + /* + * Classical integer square root approximation using shifts. + */ + for (i = 1; n > 0; n >>= 2) + i <<= 1; + + return i; +} + + +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb) { + int i = 2; + mmbuffer_t mb[3]; + + mb[0].ptr = (char *) pre; + mb[0].size = psize; + mb[1].ptr = (char *) rec; + mb[1].size = size; + if (size > 0 && rec[size - 1] != '\n') { + mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; + mb[2].size = strlen(mb[2].ptr); + i++; + } + if (ecb->outf(ecb->priv, mb, i) < 0) { + + return -1; + } + + return 0; +} + +void *xdl_mmfile_first(mmfile_t *mmf, long *size) +{ + *size = (long)mmf->size; + return mmf->ptr; +} + + +long xdl_mmfile_size(mmfile_t *mmf) +{ + return (long)mmf->size; +} + + +int xdl_cha_init(chastore_t *cha, long isize, long icount) { + + cha->head = cha->tail = NULL; + cha->isize = isize; + cha->nsize = icount * isize; + cha->ancur = cha->sncur = NULL; + cha->scurr = 0; + + return 0; +} + + +void xdl_cha_free(chastore_t *cha) { + chanode_t *cur, *tmp; + + for (cur = cha->head; (tmp = cur) != NULL;) { + cur = cur->next; + xdl_free(tmp); + } +} + + +void *xdl_cha_alloc(chastore_t *cha) { + chanode_t *ancur; + void *data; + + if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { + if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { + + return NULL; + } + ancur->icurr = 0; + ancur->next = NULL; + if (cha->tail) + cha->tail->next = ancur; + if (!cha->head) + cha->head = ancur; + cha->tail = ancur; + cha->ancur = ancur; + } + + data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; + ancur->icurr += cha->isize; + + return data; +} + + +void *xdl_cha_first(chastore_t *cha) { + chanode_t *sncur; + + if (!(cha->sncur = sncur = cha->head)) + return NULL; + + cha->scurr = 0; + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +void *xdl_cha_next(chastore_t *cha) { + chanode_t *sncur; + + if (!(sncur = cha->sncur)) + return NULL; + cha->scurr += cha->isize; + if (cha->scurr == sncur->icurr) { + if (!(sncur = cha->sncur = sncur->next)) + return NULL; + cha->scurr = 0; + } + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +long xdl_guess_lines(mmfile_t *mf, long sample) { + long nl = 0, size, tsize = 0; + char const *data, *cur, *top; + + if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { + for (top = data + size; nl < sample && cur < top; ) { + nl++; + if (!(cur = memchr(cur, '\n', top - cur))) + cur = top; + else + cur++; + } + tsize += (long) (cur - data); + } + + if (nl && tsize) + nl = xdl_mmfile_size(mf) / (tsize / nl); + + return nl + 1; +} + +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) +{ + int i1, i2; + + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return 0; + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ + if (flags & XDF_IGNORE_WHITESPACE) { + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) + return 0; + skip_ws: + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + } + } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + while (i1 < s1 && i2 < s2) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { + /* Skip matching spaces and try again */ + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + continue; + } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) + ; /* keep going */ + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + return (s2 == i2); + } + return 1; +} + +static unsigned long xdl_hash_record_with_whitespace(char const **data, + char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + for (; ptr < top && *ptr != '\n'; ptr++) { + if (XDL_ISSPACE(*ptr)) { + const char *ptr2 = ptr; + int at_eol; + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) + && ptr[1] != '\n') + ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && !at_eol) { + ha += (ha << 5); + ha ^= (unsigned long) ' '; + } + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + && !at_eol) { + while (ptr2 != ptr + 1) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr2; + ptr2++; + } + } + continue; + } + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned long xdl_hash_record(char const **data, char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + + for (; ptr < top && *ptr != '\n'; ptr++) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned int xdl_hashbits(unsigned int size) { + unsigned int val = 1, bits = 0; + + for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); + return bits ? bits: 1; +} + + +int xdl_num_out(char *out, long val) { + char *ptr, *str = out; + char buf[32]; + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + if (val < 0) { + *--ptr = '-'; + val = -val; + } + for (; val && ptr > buf; val /= 10) + *--ptr = "0123456789"[val % 10]; + if (*ptr) + for (; *ptr; ptr++, str++) + *str = *ptr; + else + *str++ = '0'; + *str = '\0'; + + return (int)(str - out); +} + + +long xdl_atol(char const *str, char const **next) { + long val, base; + char const *top; + + for (top = str; XDL_ISDIGIT(*top); top++); + if (next) + *next = top; + for (val = 0, base = 1, top--; top >= str; top--, base *= 10) + val += base * (long)(*top - '0'); + return val; +} + + +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb) { + int nb = 0; + mmbuffer_t mb; + char buf[128]; + + memcpy(buf, "@@ -", 4); + nb += 4; + + nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); + + if (c1 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c1); + } + + memcpy(buf + nb, " +", 2); + nb += 2; + + nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); + + if (c2 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c2); + } + + memcpy(buf + nb, " @@", 3); + nb += 3; + if (func && funclen) { + buf[nb++] = ' '; + if (funclen > (long)sizeof(buf) - nb - 1) + funclen = (long)sizeof(buf) - nb - 1; + memcpy(buf + nb, func, funclen); + nb += funclen; + } + buf[nb++] = '\n'; + + mb.ptr = buf; + mb.size = nb; + if (ecb->outf(ecb->priv, &mb, 1) < 0) + return -1; + + return 0; +} + +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xdfenv_t env; + + subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + + diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + + diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) + return -1; + + memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h new file mode 100644 index 000000000..714719a89 --- /dev/null +++ b/src/xdiff/xutils.h @@ -0,0 +1,49 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#if !defined(XUTILS_H) +#define XUTILS_H + + + +long xdl_bogosqrt(long n); +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb); +int xdl_cha_init(chastore_t *cha, long isize, long icount); +void xdl_cha_free(chastore_t *cha); +void *xdl_cha_alloc(chastore_t *cha); +void *xdl_cha_first(chastore_t *cha); +void *xdl_cha_next(chastore_t *cha); +long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); +unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned int xdl_hashbits(unsigned int size); +int xdl_num_out(char *out, long val); +long xdl_atol(char const *str, char const **next); +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb); +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2); + + + +#endif /* #if !defined(XUTILS_H) */ diff --git a/tests-clar/README.md b/tests-clar/README.md new file mode 100644 index 000000000..03a4d54d3 --- /dev/null +++ b/tests-clar/README.md @@ -0,0 +1,22 @@ +Writing Clar tests for libgit2 +============================== + +For information on the Clar testing framework and a detailed introduction +please visit: + +https://github.com/tanoku/clar + + +* Write your modules and tests. Use good, meaningful names. + +* Make sure you actually build the tests by setting: + + cmake -DBUILD_CLAR=ON build/ + +* Test: + + ./build/libgit2_clar + +* Make sure everything is fine. + +* Send your pull request. That's it. diff --git a/tests-clar/attr/attr_expect.h b/tests-clar/attr/attr_expect.h new file mode 100644 index 000000000..70f1ab4f5 --- /dev/null +++ b/tests-clar/attr/attr_expect.h @@ -0,0 +1,43 @@ +#ifndef __CLAR_TEST_ATTR_EXPECT__ +#define __CLAR_TEST_ATTR_EXPECT__ + +enum attr_expect_t { + EXPECT_FALSE, + EXPECT_TRUE, + EXPECT_UNDEFINED, + EXPECT_STRING +}; + +struct attr_expected { + const char *path; + const char *attr; + enum attr_expect_t expected; + const char *expected_str; +}; + +GIT_INLINE(void) attr_check_expected( + enum attr_expect_t expected, + const char *expected_str, + const char *name, + const char *value) +{ + switch (expected) { + case EXPECT_TRUE: + cl_assert_(GIT_ATTR_TRUE(value), name); + break; + + case EXPECT_FALSE: + cl_assert_(GIT_ATTR_FALSE(value), name); + break; + + case EXPECT_UNDEFINED: + cl_assert_(GIT_ATTR_UNSPECIFIED(value), name); + break; + + case EXPECT_STRING: + cl_assert_equal_s(expected_str, value); + break; + } +} + +#endif diff --git a/tests-clar/attr/file.c b/tests-clar/attr/file.c new file mode 100644 index 000000000..8866fd9bd --- /dev/null +++ b/tests-clar/attr/file.c @@ -0,0 +1,226 @@ +#include "clar_libgit2.h" +#include "attr_file.h" +#include "attr_expect.h" + +#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) +#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) + +void test_attr_file__simple_read(void) +{ + git_attr_file *file; + git_attr_assignment *assign; + git_attr_rule *rule; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0"))); + + cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2); + cl_assert(file->rules.length == 1); + + rule = get_rule(0); + cl_assert(rule != NULL); + cl_assert_equal_s("*", rule->match.pattern); + cl_assert(rule->match.length == 1); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert(assign != NULL); + cl_assert_equal_s("binary", assign->name); + cl_assert(GIT_ATTR_TRUE(assign->value)); + + git_attr_file__free(file); +} + +void test_attr_file__match_variants(void) +{ + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1"))); + + cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2); + cl_assert(file->rules.length == 10); + + /* let's do a thorough check of this rule, then just verify + * the things that are unique for the later rules + */ + rule = get_rule(0); + 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); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + cl_assert(GIT_ATTR_TRUE(assign->value)); + + 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); + + 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); + + rule = get_rule(3); + cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); + cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH); + + rule = get_rule(4); + cl_assert_equal_s("pat4.*", rule->match.pattern); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + rule = get_rule(5); + cl_assert_equal_s("*.pat5", rule->match.pattern); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + rule = get_rule(7); + cl_assert_equal_s("pat7[a-e]??[xyz]", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + assign = get_assign(rule,0); + cl_assert_equal_s("attr7", assign->name); + cl_assert(GIT_ATTR_TRUE(assign->value)); + + 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); + + git_attr_file__free(file); +} + +static void check_one_assign( + git_attr_file *file, + int rule_idx, + int assign_idx, + const char *pattern, + const char *name, + enum attr_expect_t expected, + const char *expected_str) +{ + git_attr_rule *rule = get_rule(rule_idx); + git_attr_assignment *assign = get_assign(rule, assign_idx); + + cl_assert_equal_s(pattern, rule->match.pattern); + cl_assert(rule->assigns.length == 1); + cl_assert_equal_s(name, assign->name); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + + attr_check_expected(expected, expected_str, assign->name, assign->value); +} + +void test_attr_file__assign_variants(void) +{ + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2"))); + + cl_assert_equal_s(cl_fixture("attr/attr2"), file->key + 2); + cl_assert(file->rules.length == 11); + + check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL); + check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL); + check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL); + check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL); + check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value"); + check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars"); + check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL); + check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL); + + rule = get_rule(8); + cl_assert_equal_s("pat7", rule->match.pattern); + cl_assert(rule->assigns.length == 5); + /* assignments will be sorted by hash value, so we have to do + * lookups by search instead of by position + */ + assign = git_attr_rule__lookup_assignment(rule, "multiple"); + cl_assert(assign); + cl_assert_equal_s("multiple", assign->name); + cl_assert(GIT_ATTR_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "single"); + cl_assert(assign); + cl_assert_equal_s("single", assign->name); + cl_assert(GIT_ATTR_FALSE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "values"); + cl_assert(assign); + cl_assert_equal_s("values", assign->name); + cl_assert_equal_s("1", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "also"); + cl_assert(assign); + cl_assert_equal_s("also", assign->name); + cl_assert_equal_s("a-really-long-value/*", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "happy"); + cl_assert(assign); + cl_assert_equal_s("happy", assign->name); + cl_assert_equal_s("yes!", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "other"); + cl_assert(!assign); + + rule = get_rule(9); + cl_assert_equal_s("pat8", rule->match.pattern); + cl_assert(rule->assigns.length == 2); + assign = git_attr_rule__lookup_assignment(rule, "again"); + cl_assert(assign); + cl_assert_equal_s("again", assign->name); + cl_assert(GIT_ATTR_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "another"); + cl_assert(assign); + cl_assert_equal_s("another", assign->name); + cl_assert_equal_s("12321", assign->value); + + check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL); + + git_attr_file__free(file); +} + +void test_attr_file__check_attr_examples(void) +{ + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3"))); + cl_assert_equal_s(cl_fixture("attr/attr3"), file->key + 2); + cl_assert(file->rules.length == 3); + + rule = get_rule(0); + cl_assert_equal_s("*.java", rule->match.pattern); + cl_assert(rule->assigns.length == 3); + assign = git_attr_rule__lookup_assignment(rule, "diff"); + cl_assert_equal_s("diff", assign->name); + cl_assert_equal_s("java", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "crlf"); + cl_assert_equal_s("crlf", assign->name); + cl_assert(GIT_ATTR_FALSE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "myAttr"); + cl_assert_equal_s("myAttr", assign->name); + cl_assert(GIT_ATTR_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "missing"); + cl_assert(assign == NULL); + + rule = get_rule(1); + cl_assert_equal_s("NoMyAttr.java", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_equal_s("myAttr", assign->name); + cl_assert(GIT_ATTR_UNSPECIFIED(assign->value)); + + rule = get_rule(2); + cl_assert_equal_s("README", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_equal_s("caveat", assign->name); + cl_assert_equal_s("unspecified", assign->value); + + git_attr_file__free(file); +} diff --git a/tests-clar/attr/flags.c b/tests-clar/attr/flags.c new file mode 100644 index 000000000..80c6e1171 --- /dev/null +++ b/tests-clar/attr/flags.c @@ -0,0 +1,108 @@ +#include "clar_libgit2.h" +#include "git2/attr.h" + +void test_attr_flags__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_attr_flags__bare(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo.git"); + const char *value; + + cl_assert(git_repository_is_bare(repo)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff")); + cl_assert(GIT_ATTR_UNSPECIFIED(value)); +} + +void test_attr_flags__index_vs_workdir(void) +{ + git_repository *repo = cl_git_sandbox_init("attr_index"); + const char *value; + + cl_assert(!git_repository_is_bare(repo)); + + /* wd then index */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.md", "bar")); + cl_assert(GIT_ATTR_FALSE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.md", "blargh")); + cl_assert_equal_s(value, "goop"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.txt", "foo")); + cl_assert(GIT_ATTR_FALSE(value)); + + /* index then wd */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.md", "bar")); + cl_assert(GIT_ATTR_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.md", "blargh")); + cl_assert_equal_s(value, "garble"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.txt", "foo")); + cl_assert(GIT_ATTR_TRUE(value)); +} + +void test_attr_flags__subdir(void) +{ + git_repository *repo = cl_git_sandbox_init("attr_index"); + const char *value; + + /* wd then index */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.md", "bar")); + cl_assert_equal_s(value, "1234"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "another")); + cl_assert_equal_s(value, "one"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "again")); + cl_assert(GIT_ATTR_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "beep")); + cl_assert_equal_s(value, "10"); + + /* index then wd */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.md", "bar")); + cl_assert_equal_s(value, "1337"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "another")); + cl_assert_equal_s(value, "one"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "again")); + cl_assert(GIT_ATTR_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "beep")); + cl_assert_equal_s(value, "5"); +} + diff --git a/tests-clar/attr/lookup.c b/tests-clar/attr/lookup.c new file mode 100644 index 000000000..40aac0b6e --- /dev/null +++ b/tests-clar/attr/lookup.c @@ -0,0 +1,262 @@ +#include "clar_libgit2.h" +#include "attr_file.h" + +#include "attr_expect.h" + +void test_attr_lookup__simple(void) +{ + git_attr_file *file; + git_attr_path path; + const char *value = NULL; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0"))); + cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2); + cl_assert(file->rules.length == 1); + + cl_git_pass(git_attr_path__init(&path, "test", NULL)); + cl_assert_equal_s("test", path.path); + cl_assert_equal_s("test", path.basename); + cl_assert(!path.is_dir); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); + cl_assert(GIT_ATTR_TRUE(value)); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); + cl_assert(!value); + + git_attr_path__free(&path); + git_attr_file__free(file); +} + +static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir) +{ + git_attr_path path; + const char *value = NULL; + struct attr_expected *c; + int error; + + for (c = cases; c->path != NULL; c++) { + cl_git_pass(git_attr_path__init(&path, c->path, NULL)); + + if (force_dir) + path.is_dir = 1; + + error = git_attr_file__lookup_one(file,&path,c->attr,&value); + cl_git_pass(error); + + attr_check_expected(c->expected, c->expected_str, c->attr, value); + + git_attr_path__free(&path); + } +} + +void test_attr_lookup__match_variants(void) +{ + git_attr_file *file; + git_attr_path path; + + struct attr_expected dir_cases[] = { + { "pat2", "attr2", EXPECT_TRUE, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, + { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL }, + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } + }; + + struct attr_expected cases[] = { + /* pat0 -> simple match */ + { "pat0", "attr0", EXPECT_TRUE, NULL }, + { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL }, + { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL }, + { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL }, + { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL }, + { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL }, + /* negative match */ + { "pat0", "attr1", EXPECT_TRUE, NULL }, + { "pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL }, + { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL }, + { "misc", "attr1", EXPECT_TRUE, NULL }, + /* dir match */ + { "pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, + /* path match */ + { "pat3file", "attr3", EXPECT_UNDEFINED, NULL }, + { "/pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, + { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, + /* pattern* match */ + { "pat4.txt", "attr4", EXPECT_TRUE, NULL }, + { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL }, + { "pat4.", "attr4", EXPECT_TRUE, NULL }, + { "pat4", "attr4", EXPECT_UNDEFINED, NULL }, + /* *pattern match */ + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL }, + { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL }, + /* glob match with slashes */ + { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, + /* complex pattern */ + { "pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "pat7e__x", "attr7", EXPECT_TRUE, NULL }, + { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */ + { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL }, + { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL }, + /* pattern with spaces */ + { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL }, + { "spaces", "attr8", EXPECT_UNDEFINED, NULL }, + /* pattern at eof */ + { "pat9", "attr9", EXPECT_TRUE, NULL }, + { "/eof/pat9", "attr9", EXPECT_TRUE, NULL }, + { "pat", "attr9", EXPECT_UNDEFINED, NULL }, + { "at9", "attr9", EXPECT_UNDEFINED, NULL }, + { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1"))); + cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2); + cl_assert(file->rules.length == 10); + + cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL)); + cl_assert_equal_s("pat0", path.basename); + + run_test_cases(file, cases, 0); + run_test_cases(file, dir_cases, 1); + + git_attr_file__free(file); + git_attr_path__free(&path); +} + +void test_attr_lookup__assign_variants(void) +{ + git_attr_file *file; + + struct attr_expected cases[] = { + /* pat0 -> simple assign */ + { "pat0", "simple", EXPECT_TRUE, NULL }, + { "/testing/pat0", "simple", EXPECT_TRUE, NULL }, + { "pat0", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL }, + /* negative assign */ + { "pat1", "neg", EXPECT_FALSE, NULL }, + { "/testing/pat1", "neg", EXPECT_FALSE, NULL }, + { "pat1", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL }, + /* forced undef */ + { "pat1", "notundef", EXPECT_TRUE, NULL }, + { "pat2", "notundef", EXPECT_UNDEFINED, NULL }, + { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL }, + { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL }, + /* assign value */ + { "pat3", "assigned", EXPECT_STRING, "test-value" }, + { "pat3", "notassigned", EXPECT_UNDEFINED, NULL }, + /* assign value */ + { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" }, + { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL }, + /* empty assignments */ + { "pat5", "empty", EXPECT_TRUE, NULL }, + { "pat6", "negempty", EXPECT_FALSE, NULL }, + /* multiple assignment */ + { "pat7", "multiple", EXPECT_TRUE, NULL }, + { "pat7", "single", EXPECT_FALSE, NULL }, + { "pat7", "values", EXPECT_STRING, "1" }, + { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" }, + { "pat7", "happy", EXPECT_STRING, "yes!" }, + { "pat8", "again", EXPECT_TRUE, NULL }, + { "pat8", "another", EXPECT_STRING, "12321" }, + /* bad assignment */ + { "patbad0", "simple", EXPECT_UNDEFINED, NULL }, + { "patbad0", "notundef", EXPECT_TRUE, NULL }, + { "patbad1", "simple", EXPECT_UNDEFINED, NULL }, + /* eof assignment */ + { "pat9", "at-eof", EXPECT_FALSE, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2"))); + cl_assert(file->rules.length == 11); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} + +void test_attr_lookup__check_attr_examples(void) +{ + git_attr_file *file; + + struct attr_expected cases[] = { + { "foo.java", "diff", EXPECT_STRING, "java" }, + { "foo.java", "crlf", EXPECT_FALSE, NULL }, + { "foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" }, + { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "README", "caveat", EXPECT_STRING, "unspecified" }, + { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" }, + { "README", "missing", EXPECT_UNDEFINED, NULL }, + { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3"))); + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} + +void test_attr_lookup__from_buffer(void) +{ + git_attr_file *file; + + struct attr_expected cases[] = { + { "abc", "foo", EXPECT_TRUE, NULL }, + { "abc", "bar", EXPECT_TRUE, NULL }, + { "abc", "baz", EXPECT_TRUE, NULL }, + { "aaa", "foo", EXPECT_TRUE, NULL }, + { "aaa", "bar", EXPECT_UNDEFINED, NULL }, + { "aaa", "baz", EXPECT_TRUE, NULL }, + { "qqq", "foo", EXPECT_UNDEFINED, NULL }, + { "qqq", "bar", EXPECT_UNDEFINED, NULL }, + { "qqq", "baz", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__new(&file, 0, NULL, NULL)); + + cl_git_pass(git_attr_file__parse_buffer(NULL, "a* foo\nabc bar\n* baz", file)); + + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c new file mode 100644 index 000000000..c37ff544a --- /dev/null +++ b/tests-clar/attr/repo.c @@ -0,0 +1,273 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "git2/attr.h" +#include "attr.h" + +#include "attr_expect.h" + +static git_repository *g_repo = NULL; + +void test_attr_repo__initialize(void) +{ + /* Before each test, instantiate the attr repo from the fixtures and + * rename the .gitted to .git so it is a repo with a working dir. + * Also rename gitattributes to .gitattributes, because it contains + * macro definitions which are only allowed in the root. + */ + g_repo = cl_git_sandbox_init("attr"); +} + +void test_attr_repo__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_attr_repo__get_one(void) +{ + struct attr_expected test_cases[] = { + { "root_test1", "repoattr", EXPECT_TRUE, NULL }, + { "root_test1", "rootattr", EXPECT_TRUE, NULL }, + { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "negattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "repoattr", EXPECT_TRUE, NULL }, + { "root_test2", "rootattr", EXPECT_FALSE, NULL }, + { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "multiattr", EXPECT_FALSE, NULL }, + { "root_test3", "repoattr", EXPECT_TRUE, NULL }, + { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, + { "root_test3", "multiattr", EXPECT_STRING, "3" }, + { "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" }, + { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" }, + { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL }, + { "does-not-exist", "foo", EXPECT_STRING, "yes" }, + { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL }, + { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" }, + { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL }, + { NULL, NULL, 0, NULL } + }, *scan; + + for (scan = test_cases; scan->path != NULL; scan++) { + const char *value; + cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); + attr_check_expected(scan->expected, scan->expected_str, scan->attr, value); + } + + cl_assert(git_attr_cache__is_cached(g_repo, 0, ".git/info/attributes")); + cl_assert(git_attr_cache__is_cached(g_repo, 0, ".gitattributes")); + cl_assert(git_attr_cache__is_cached(g_repo, 0, "sub/.gitattributes")); +} + +void test_attr_repo__get_many(void) +{ + const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; + const char *values[4]; + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test2", 4, names)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_FALSE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/subdir_test1", 4, names)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert_equal_s("yes", values[3]); +} + +static int count_attrs( + const char *name, + const char *value, + void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(value); + + *((int *)payload) += 1; + + return 0; +} + +void test_attr_repo__foreach(void) +{ + int count; + + count = 0; + cl_git_pass(git_attr_foreach( + g_repo, 0, "root_test1", &count_attrs, &count)); + cl_assert(count == 2); + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test1", + &count_attrs, &count)); + cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */ + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt", + &count_attrs, &count)); + cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */ +} + +void test_attr_repo__manpage_example(void) +{ + const char *value; + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "foo")); + cl_assert(GIT_ATTR_TRUE(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar")); + cl_assert(GIT_ATTR_UNSPECIFIED(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz")); + cl_assert(GIT_ATTR_FALSE(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "merge")); + cl_assert_equal_s("filfre", value); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "frotz")); + cl_assert(GIT_ATTR_UNSPECIFIED(value)); +} + +void test_attr_repo__macros(void) +{ + const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" }; + const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; + const char *names3[3] = { "macro2", "multi2", "multi3" }; + const char *values[5]; + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 5, names)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); + cl_assert(GIT_ATTR_FALSE(values[3])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[4])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); + cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + cl_assert_equal_s("77", values[4]); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3)); + + cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_FALSE(values[1])); + cl_assert_equal_s("answer", values[2]); +} + +void test_attr_repo__bad_macros(void) +{ + const char *names[6] = { "rootattr", "positive", "negative", + "firstmacro", "secondmacro", "thirdmacro" }; + const char *values[6]; + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names)); + + /* these three just confirm that the "mymacro" rule ran */ + cl_assert(GIT_ATTR_UNSPECIFIED(values[0])); + cl_assert(GIT_ATTR_TRUE(values[1])); + cl_assert(GIT_ATTR_FALSE(values[2])); + + /* file contains: + * # let's try some malicious macro defs + * [attr]firstmacro -thirdmacro -secondmacro + * [attr]secondmacro firstmacro -firstmacro + * [attr]thirdmacro secondmacro=hahaha -firstmacro + * macro_bad firstmacro secondmacro thirdmacro + * + * firstmacro assignment list ends up with: + * -thirdmacro -secondmacro + * secondmacro assignment list expands "firstmacro" and ends up with: + * -thirdmacro -secondmacro -firstmacro + * thirdmacro assignment don't expand so list ends up with: + * secondmacro="hahaha" + * + * macro_bad assignment list ends up with: + * -thirdmacro -secondmacro firstmacro && + * -thirdmacro -secondmacro -firstmacro secondmacro && + * secondmacro="hahaha" thirdmacro + * + * so summary results should be: + * -firstmacro secondmacro="hahaha" thirdmacro + */ + cl_assert(GIT_ATTR_FALSE(values[3])); + cl_assert_equal_s("hahaha", values[4]); + cl_assert(GIT_ATTR_TRUE(values[5])); +} + +#define CONTENT "I'm going to be dynamically processed\r\n" \ + "And my line endings...\r\n" \ + "...are going to be\n" \ + "normalized!\r\n" + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void add_to_workdir(const char *filename, const char *content) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&buf, "attr", filename)); + cl_git_rewritefile(git_buf_cstr(&buf), content); + + git_buf_free(&buf); +} + +static void assert_proper_normalization(git_index *index, const char *filename, const char *expected_sha) +{ + int index_pos; + git_index_entry *entry; + + add_to_workdir(filename, CONTENT); + cl_git_pass(git_index_add(index, filename, 0)); + + index_pos = git_index_find(index, filename); + cl_assert(index_pos >= 0); + + entry = git_index_get(index, index_pos); + cl_assert_equal_i(0, git_oid_streq(&entry->oid, expected_sha)); +} + +void test_attr_repo__staging_properly_normalizes_line_endings_according_to_gitattributes_directives(void) +{ + git_index* index; + + cl_git_pass(git_repository_index(&index, g_repo)); + + add_to_workdir(".gitattributes", GITATTR); + + assert_proper_normalization(index, "text.txt", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); + assert_proper_normalization(index, "huh.dunno", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); + assert_proper_normalization(index, "binary.data", "66eeff1fcbacf589e6d70aa70edd3fce5be2b37c"); + + git_index_free(index); +} diff --git a/tests-clar/buf/basic.c b/tests-clar/buf/basic.c new file mode 100644 index 000000000..d558757a9 --- /dev/null +++ b/tests-clar/buf/basic.c @@ -0,0 +1,29 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static const char *test_string = "Have you seen that? Have you seeeen that??"; + +void test_buf_basic__resize(void) +{ + git_buf buf1 = GIT_BUF_INIT; + git_buf_puts(&buf1, test_string); + cl_assert(git_buf_oom(&buf1) == 0); + cl_assert_equal_s(git_buf_cstr(&buf1), test_string); + + git_buf_puts(&buf1, test_string); + cl_assert(strlen(git_buf_cstr(&buf1)) == strlen(test_string) * 2); + git_buf_free(&buf1); +} + +void test_buf_basic__printf(void) +{ + git_buf buf2 = GIT_BUF_INIT; + git_buf_printf(&buf2, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_buf_oom(&buf2) == 0); + cl_assert_equal_s(git_buf_cstr(&buf2), "shoop da 23 "); + + git_buf_printf(&buf2, "%s %d", "woop", 42); + cl_assert(git_buf_oom(&buf2) == 0); + cl_assert_equal_s(git_buf_cstr(&buf2), "shoop da 23 woop 42"); + git_buf_free(&buf2); +} diff --git a/tests-clar/clar b/tests-clar/clar new file mode 100755 index 000000000..873dc3b0c --- /dev/null +++ b/tests-clar/clar @@ -0,0 +1,311 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from string import Template +import re, fnmatch, os + +VERSION = "0.10.0" + +TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{" + +EVENT_CB_REGEX = re.compile( + r"^(void\s+clar_on_(\w+)\(\s*void\s*\))\s*\{", + re.MULTILINE) + +SKIP_COMMENTS_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + +CLAR_HEADER = """ +/* + * Clar v%s + * + * This is an autogenerated file. Do not modify. + * To add new unit tests or suites, regenerate the whole + * file with `./clar` + */ +""" % VERSION + +CLAR_EVENTS = [ + 'init', + 'shutdown', + 'test', + 'suite' +] + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-c', '--clar-path', dest='clar_path') + parser.add_option('-v', '--report-to', dest='print_mode', default='default') + + options, args = parser.parse_args() + + for folder in args or ['.']: + builder = ClarTestBuilder(folder, + clar_path = options.clar_path, + print_mode = options.print_mode) + + builder.render() + + +class ClarTestBuilder: + def __init__(self, path, clar_path = None, print_mode = 'default'): + self.declarations = [] + self.suite_names = [] + self.callback_data = {} + self.suite_data = {} + self.event_callbacks = [] + + self.clar_path = os.path.abspath(clar_path) if clar_path else None + + self.path = os.path.abspath(path) + self.modules = [ + "clar_sandbox.c", + "clar_fixtures.c", + "clar_fs.c" + ] + + self.modules.append("clar_print_%s.c" % print_mode) + + print("Loading test suites...") + + for root, dirs, files in os.walk(self.path): + module_root = root[len(self.path):] + module_root = [c for c in module_root.split(os.sep) if c] + + tests_in_module = fnmatch.filter(files, "*.c") + + for test_file in tests_in_module: + full_path = os.path.join(root, test_file) + test_name = "_".join(module_root + [test_file[:-2]]) + + with open(full_path) as f: + self._process_test_file(test_name, f.read()) + + if not self.suite_data: + raise RuntimeError( + 'No tests found under "%s"' % path) + + def render(self): + main_file = os.path.join(self.path, 'clar_main.c') + with open(main_file, "w") as out: + out.write(self._render_main()) + + header_file = os.path.join(self.path, 'clar.h') + with open(header_file, "w") as out: + out.write(self._render_header()) + + print ('Written Clar suite to "%s"' % self.path) + + ##################################################### + # Internal methods + ##################################################### + + def _render_cb(self, cb): + return '{"%s", &%s}' % (cb['short_name'], cb['symbol']) + + def _render_suite(self, suite, index): + template = Template( +r""" + { + ${suite_index}, + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count} + } +""") + + callbacks = {} + for cb in ['initialize', 'cleanup']: + callbacks[cb] = (self._render_cb(suite[cb]) + if suite[cb] else "{NULL, NULL}") + + return template.substitute( + suite_index = index, + clean_name = suite['name'].replace("_", "::"), + initialize = callbacks['initialize'], + cleanup = callbacks['cleanup'], + cb_ptr = "_clar_cb_%s" % suite['name'], + cb_count = suite['cb_count'] + ).strip() + + def _render_callbacks(self, suite_name, callbacks): + template = Template( +r""" +static const struct clar_func _clar_cb_${suite_name}[] = { + ${callbacks} +}; +""") + callbacks = [ + self._render_cb(cb) + for cb in callbacks + if cb['short_name'] not in ('initialize', 'cleanup') + ] + + return template.substitute( + suite_name = suite_name, + callbacks = ",\n\t".join(callbacks) + ).strip() + + def _render_event_overrides(self): + overrides = [] + for event in CLAR_EVENTS: + if event in self.event_callbacks: + continue + + overrides.append( + "#define clar_on_%s() /* nop */" % event + ) + + return '\n'.join(overrides) + + def _render_header(self): + template = Template(self._load_file('clar.h')) + + declarations = "\n".join( + "extern %s;" % decl + for decl in sorted(self.declarations) + ) + + return template.substitute( + extern_declarations = declarations, + ) + + def _render_main(self): + template = Template(self._load_file('clar.c')) + suite_names = sorted(self.suite_names) + + suite_data = [ + self._render_suite(self.suite_data[s], i) + for i, s in enumerate(suite_names) + ] + + callbacks = [ + self._render_callbacks(s, self.callback_data[s]) + for s in suite_names + ] + + callback_count = sum( + len(cbs) for cbs in self.callback_data.values() + ) + + return template.substitute( + clar_modules = self._get_modules(), + clar_callbacks = "\n".join(callbacks), + clar_suites = ",\n\t".join(suite_data), + clar_suite_count = len(suite_data), + clar_callback_count = callback_count, + clar_event_overrides = self._render_event_overrides(), + ) + + def _load_file(self, filename): + if self.clar_path: + filename = os.path.join(self.clar_path, filename) + with open(filename) as cfile: + return cfile.read() + + else: + import zlib, base64, sys + content = CLAR_FILES[filename] + + if sys.version_info >= (3, 0): + content = bytearray(content, 'utf_8') + content = base64.b64decode(content) + content = zlib.decompress(content) + return str(content, 'utf-8') + else: + content = base64.b64decode(content) + return zlib.decompress(content) + + def _get_modules(self): + return "\n".join(self._load_file(f) for f in self.modules) + + def _skip_comments(self, text): + def _replacer(match): + s = match.group(0) + return "" if s.startswith('/') else s + + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) + + def _process_test_file(self, suite_name, contents): + contents = self._skip_comments(contents) + + self._process_events(contents) + self._process_declarations(suite_name, contents) + + def _process_events(self, contents): + for (decl, event) in EVENT_CB_REGEX.findall(contents): + if event not in CLAR_EVENTS: + continue + + self.declarations.append(decl) + self.event_callbacks.append(event) + + def _process_declarations(self, suite_name, contents): + callbacks = [] + initialize = cleanup = None + + regex_string = TEST_FUNC_REGEX % suite_name + regex = re.compile(regex_string, re.MULTILINE) + + for (declaration, symbol, short_name) in regex.findall(contents): + data = { + "short_name" : short_name, + "declaration" : declaration, + "symbol" : symbol + } + + if short_name == 'initialize': + initialize = data + elif short_name == 'cleanup': + cleanup = data + else: + callbacks.append(data) + + if not callbacks: + return + + tests_in_suite = len(callbacks) + + suite = { + "name" : suite_name, + "initialize" : initialize, + "cleanup" : cleanup, + "cb_count" : tests_in_suite + } + + if initialize: + self.declarations.append(initialize['declaration']) + + if cleanup: + self.declarations.append(cleanup['declaration']) + + self.declarations += [ + callback['declaration'] + for callback in callbacks + ] + + callbacks.sort(key=lambda x: x['short_name']) + self.callback_data[suite_name] = callbacks + self.suite_data[suite_name] = suite + self.suite_names.append(suite_name) + + print(" %s (%d tests)" % (suite_name, tests_in_suite)) + + + +CLAR_FILES = { +"clar.c" : r"""eJytGWtv20byM/krNs4lpmxasZTD4c5OXAS55mC0dYA8kAKJQazIlbUORcpcMrHb6r93ZvbB5UN2D2g+xOa8dmZ2nuvHskjzJhPsBVdKVPV0dRY+djAl6uv1pgers1wuBjBZ9kGVLK66sDWvVwNGXhFV+OyAVeKmkZXI2LKsmOJFtihvQQg7eOaz3Kln9d1GqJ4kAKuakwEAXmZiyZJP5xfP5+HjwFF9l0VWftesLdTo3gLUSuQ538geOAPlUnNCAAfIQrDkl1fnF8nr1yxJ0kykuYdCdaIN2BzDrxOWdL9buvVXEGwQ6zITQNqCPLp05YAs8T5aCp6mQqmuqCHM17DKmk0EP0g994FGyGVBPkx+Ob/436fn8yQBYLCp+NWas7Rcr0VRRxAJMdsjdz2f76FkT3SRbu6iuozZsirXMavLRMnfQCWDShQhDdhSJR/efbx4/erDj76wT8nbn9jx3IO8T87f//f8XXQ7YVF0y56yBCBvADJhj16y444mxQZCsU7ETbRolrH6LV6u65jHC7RZ45agi8G58x0ViBK5EqMS7a9LJCoyuQwDjE10HFjZpLW+dfb+w6sPyYfT8LGR1Anb71xiUDHIAPx1I7NoPqGgb+maQkKu6HjsRZ53nSN69fXpqUM6t2m0l+a8mq72whDpZMq+lRLSUSXVOkrLQtUQhLxiB4kqmyoVk9M+XVrCnY9QxswHZgIS4NQd4qPCpbytm0okGLAdSQuuemIsacHXQosjE9GGRFQVFJHfw8BnqOHc0xC8WjP8NSma9UJUp10i1cha9GBLmQvDmIOnxxnpyGStrhBu7UwruallWYB6wVC/g0Lcgkbb1heGpqc4T2v5TSRG/xGMUVqrSB/6BGXNLWueO5DngrRsitpCKrEpq9qQJWWR3+1Q28keweVcGRHITIERHeRlCuenueBFs5lEBD2AO9P4Lhqu/i4veYbs0H4SyExWV3y9KdH31iAHSETBF7kA8i2URVCkFwnLpkj7/sSIOXXKbaDkkUoTexUtN/kS2fFQ6B7i9nRU1OBEWcha8hxK2xjWmOpkDQgoVsnDICHpXhbqCMXiDRRMzaHT/mCaYtXwM9LDoj5R99pj1kaLAQwSgZJL9RLdE6tWTQ0ttXhYNAJ0ihBgt0giInmQZlalXUG4W0xZoDpRP//JIK2NVwM0YMkhz7P7RJJF0biXfJspUu4TxBeQaR1BUDhiNp1OJ/3bNZPQjtttCoO3EWwoUBef3aJR9o/fBGJ5ni94+pWV38CVEooVHvCP37WPkSRxmC3xvWrq8koUouI1TGnoPpbxmrPFHR3lsVvZyNip8sPcStrf1edL9hKSjcE/I0jDt50SqTPC49MRBaw+kwZuT0fZrH59zi7cJNrrshLaWkxNrOaqdxUhMXfKp/Z3aCsHHDEbr5f0I7atIAw0+KVWdOoq7fcVxCaLNBZmnIuPP/88wcIUICPQE+boTIsJgmFCHR7GzOZMECwrISLD4/WpHo4+rUZGNFxp4CvndG0rP9P6QakKR9zUFNTJovuLX7wb3dbWe4hss7FXoGuPqp263TYZao+VRjc0XJMNO42+S5C6ZJFekaI+6YS9xDGU7gfJWo2PzqDduAsEdNDD4dEARi3ct+fzUXWOrTrmIvx26p81gh5jsR14YsUa+EB3H671Nkq6RnB4iFBa7tZQTRgv7hiddQSes22QrUW9KjPKqjEdXTiNIa2yjsj3xHCimdjc6GYrOpmG807mmB6Ct6Tvg+o8fvbtJGA0DCx25gKPLnuyOyf83jIsk7rRUDTflzXgBSI8OusNENI65tGDnuk2OyNOdzjzYdubyxlN6kWAP5e2OplhacRNJoZx848kfUHNfOHbYqYfdngodV51DjK244/P8nJqDgq6BeepQcfsqTPEVRIHs4WD0m5LrwnsGLezWkDmVbyS+R3LpNLJN9oI8DZlcZWPXecDFW5w27uc9dcdPXSRJQh7DiKt/qJztm59HNjfKH7VnZJ4dUXG6K002vuIFCfsiWKfS+o76vJL8aXYixlSnraEbzX2BHA+mLEj9euvX+ov9bumYBi9rF4JbT3TIx2DUEIrGdANmG8YQ+a3yKgzgfyiQArsxyuoJrzQiWt4xS2Mz0ezHem74ZUSCaiuaG6FX9LYWI6mf2vngE6Qz3SQI7kX185jDb6xABlKgKiltoAZbFGfjy+xGO8f7VNp8VxPHMeXupOo77JOVx7b7NIcBBs121f7J/QVmIkbDkQjJhCUdZk7LnbI5hAG9jNms2MaDjoaYdcz2iK9PiawJeia6AN9xtFMf5EzrnXGX4MzBmMdeuaaDNRngV4pdNxWkc4Aea1z3++/7ZlGgSBYVIJ/NR/b0P6H0pHwBT4gBbs8aggjbdQEGc5eDtW2Z49M/Xvv/TB9krGshNm7KGFGuZWqnlIeAFYf5oWeVbTXMp52PACMRk1nJU4P5rZv9k/a9jXsAWZMdbwhPXrxJq9PdoYYKbX18gJs1WlBNWV3QvS3U5BEkTc2osddpL9ZAmZvLwy8YcXbeiZ0mzrcR27iDW18rC7tezOVEaqU4F/YdKYm//1b2HrDIinujkY7oePP2lbaFgbtAzK+O6N0K7g/2betOxh0xtE8kZMHw0NedkzobfHh+GSz2/OG2nteMjfhJgMruoW2KyuqUom6qQo2FESltq2xif4LRaQLKTSXTGJriIcvdXH7UhfveKLrwb0FyDCrVdnkWUJhQsG6a3NzUWcVwivQNvnbHEZzmUazmPbQchkN5E16YWF7fX96dzNA5/jBBjac+h1OL4m+hJH9zeEMi5keBhPFaYfCPKc4Qv/ZytLZUWUY/S0NXiOQmHdXA/SeXgFnHgMNzt2sVV0/xGrneNfr+2W4/eIApf8M0l2Jx6Yw3GxGwl8vPNSo/BBym+CjXWucaRgjVYq6AJQqOETbdsJ4b3ViFZdK6KnlNhWk91QXrk7/oE6Ql8XV2NIaM1vfTOIxP/EScdOAlSrqPZfPermk5v9vSu5KOQSuOc4uMJKomU0F9scfTM3bSvmDRSoYOE7cgKAw1+ZmEHDLD8nz5qxFs/z8z+P//OsSvdP7UxFDRMz29p+ofRq04Cd0ZiPYrRe2MB3HFK+xthJvKmYkpmPYQ/6VpsDJmXGL/Pv8iQ8RM7REznsumM3/PeoBgIMDYEQBpicZGA9qIfffY3yiRO3eIcxjfP9vBLF+6zwoN/ym8Tei/gtB+6R+/yOBFkTNxTz6rcusyemNE93m/qK75rIYTC809lyiGvhIaXpXO+x0mvw2/BMhekzB""", +"clar_print_default.c" : r"""eJyFU8Fu2zAMPdtfwQUwIgVuenew9tZTsMuwU1sYqiW3AhzJkOhswNB/n0Q5rRws6Ukmxff4RD6XHgXqDo5WS+gG4drRaYOtNhpZ+ABUHtvOTgZriLGfNKpTorPGI3RvwsEmXRhxUJ6Xf8uCRUr+Cd+VBVH3bLW3QioJlUxsvoHKP5lVDbEjX3TIWTOGnygcKhlAIftelhde4d8mlPa3+folMaGcsy4lLr0gpTLkRy4D78pPoU8maSxIlVOjddhSrWdXpVMN6TbT4TRpj27qMJVRAWzoILmnlhAGy+FB6GFyqqG5Bgqeq6p801QeWOU5PIagks/weIPhiOVlURDrzR09NIvjLGK4Mhak8p3TI2q7gPR6yBGDNmF90+FFuTOeObvQBScjzHVpqAf/SlW6BzZfZM3h23f48Wu/54H+Ek9Wzpfbue4fa6JSlts8SQ9+TJ7JXpISfZi7kuf+iYDdMkOYzNJVF/QmNNzD+mENDay36y/00YbY///D3ObaSPWHVN1uwFg7wuZ2aWeqOLN4kn2tv3gJhl70D9uqYbvdUrOjaAcdroR7HXcU+vjnshjXkBZbHPt5Bh5lWBjla4LwhFFGsjl8L/8BsUiTTQ==""", +"clar_print_tap.c" : r"""eJyNVE1vnDAQPcOvmGWFBAiQot6yaqr2HFU9tLdKyAGzscLayDbbVlX+e8cDJPbuJtsTzPObmTcfdmwss6KFoxIdtAPTzaiFtI2Qwmb4A5Yb27RqkrYEZ5tJWL4CrZLGQvvINBTzgWQHbvL4bxxlLmT+6r5bIY94gq08ktBnyffP3+DItRFKws2HnzLJd/FzHL8h2TxOtlO/5HXZDuBaKz0D/yM3xDznXRxHoodsEwSMXmrYwsiM4R2wYYC0I2GZybGY0hOJhUV8MDxw7JkY0BGd2EHJ/am3l7BEvyiMtoa5qeu0O8/2dhspLPVQTod1xMbqqbUzjQhQ0MdrHbJdL9a8AFVVzSPzMJy5YXsOt5Ca1yKqu7mWg9mHdMNx/ML+uaVenEWj0QCcRSM8pLri4QLV4SGzx6ZfYjo8ZA5CrszOZzq8wXY8cJ2v67Ecddy0WozWbfTmI3z9cX/vLwuARzgV4B3lYafrur52OZSk1fEvLO2Du4bzhZhNUj0D8/rRhNdUqXFLWC3CUPiyop8gkcqCekqwGQl+3Jkf8MXEdHFE8kmc5qPSy86Z7EoFNNbs8pvj33IhO/470L2FoihQNWTbtMudQY313X3X92WwB5QcyMC9Ld0QKOeRNYPAI6b3445MjIQOzi5hWfF+UWbwxZrwRUq+YCMBfzdAO348JVAKFyKfY3LZZYv5HP8D5Mbj9w==""", +"clar_sandbox.c" : r"""eJydVWtP4kAU/dz+iism0gpKfWQ3G9YPm+gasioEMJgomdR2KhPplMwM7KLxv++dTqEP0DVrTKjcO+eec+6cKpWvWADBxBdAgqkvyMxXk/tT79uXcdu2pSkzrmwmycKfspCoeJY2OUHCpTJH9/UXrv1qW4PhjyEZglR42mIROBrC0eUm7Enlws4ZeK5tWYKqueDgrfp2BqQzOO/08cChVCROQupW+7Jnxw8CKmWGOiLdXy6cadi2/VbiHDFe5JsyfZxHERVNkOyFEgVTyp8M9V0W8ZBGQEadm5Nj28pwjMqse4EGBcmcKziD03alx+BTvkCjhLwfYw8aYtWG1z3UVWuCfko/Lszn7eCi3+t3f3auLmo2WG8oEaxsEtN6o0SAwxDHawOD7/n4NjQazE3hK7Ox+YkqfHDWRNgYjbGMyfilNlWfUozPqZ6SVjbXq1vNCJQpeDBbOivvsNRcOaehC0uyrDcbf22rtQ+dCNSE6m4mEh5TtC1MqOR19NNfgs+XasL4UxOUWIJKYC4ptHA+7Lfsd0jVdL2W8arSMsUSswIxJLVLp5Ia6EuqhjSe9TSocz7q9s9dc6wJBq5y+XYpD1lkdA0nTIJcSkXjtaApe6YooKRFiw/mQqTCmaCBSrD4gbjDd5UdfiRr9efBUTEAi4SFkEZ6zqXPw8fkj6O/S2OqCRTy7o11gOoPXj1XjVcDI1FMRDBBFcgSaRYMiSQRcQGsmkL0k01DklEwStc8CrdXF4jy2TRNTi3F09bcpT81nbZ1ZFcvjXLAcw4m3klUpOVigIpvHu2WbSEYTkO/8aEsoqr+FXD1PBExLu2FpnT1onvdQecOMKm/fRGCnPpyQmW65EKUrY0oaxF5iKv7YNk+HtJ9WFalBPVWfR219SIqGFrZARyN9RsX+82gcr3RyMH0PVpdu7wLGpppM1/ONmdxDDZllgF6xjgNHUKuOzeXo5NjQtyMXPyMkZmVjqLMm9urq4296P74Wd+34la9r5638S9EH8BkF0enKytPJfKf92ML7v8QWb1i8NQn5a5XmOe6HKEU4fMhhr29banbngCNYpJdJLrVixK9v7GvgW8=""", +"clar_fixtures.c" : r"""eJyFUV1LwzAUfW5+xZU9rLUVJ4ggZQ9DFAUfZEwQSglZmrBAl5Qkk6n43236tWbKfMvNOfecc+81llhBgSppLNAN0XCOuNjbnWa4InYTjpE1MSzxuD1Vki2L0BcKTKfn0EYgu57d3uRpjYhPhi1opSwumUwRCvo3zMFYXT9C5xA5stWSVh9hI5FAa+wUFG//osgJCA5tmQ1SF3CVw9kcppfTCAWBj8ZxDg3UN4/zZ7MaHBrHSBw7vpcJ4mGS5Ijtai9qnannNqk1q7myXU+KvhGaCF4wDnfPiyV+eHpbvS7v8cti9YjGq6Yl7lzCkxfo1L0j/lJOwOtrUrwrUcDBBRsii7Xan3bjBlNVL2WUzuMkgGlJdLuIP21oyYjcVf/a6G3ozXTQPRqmsZkwWQiOfgAVGffP""", +"clar_fs.c" : r"""eJylVdtu20YQfSa/YkAD8TKWY8dJX6L0wXDEVqgsBhINN7UFhiGX1qIkl9hd+dLG/57ZCynJUWEkfZE0s7NnZufMGe2xsqAlpJfj6ZsT399DgzUUojhKo8npb3Mg+ud8PBlNE/hq/NP4LJ5G49n5aTKOp71zNJvFs4vx06DzPz6MZ6HvS5UplkO+zAS89EtWUd7KtM3UkuS8kcqdGE/o/+t71tYm/ArTi8lk6HuS/UNTBRVtbtRyAGzo+x4rgaQ2zMaFvucJqlaicdd8z15AHKkE/rbxIQI6+DqrKp4TF3YAJ2GH/AxwTeu8fTBRA0jtl0Xp0K+sucAsx9suzPPauX2v5AIIMxYweO9AhnBwwELAbvTFXLGFrmf/aF+X4/Uu2L++3scEjwjmitRnQ/+x7/0tZ0XXecIaBTUv6AC22i/5SuRPnQWVynAy/z3CSYg/zpPZxVkCJQLp4m2YvYqVbJHrEHU7bJgG+y7IZNBQf1HBz2nNxQN5oeEHoDnnJdlOHYa2aa18dRetmlxziI8ZOl8bCV5ruk3u3ptw9OlUnaeMquxGorOfd/OcKs2kpEKlBFuMibHUuKUCm8gbW1aoOTge4HFwyZqC30l4EgdlhmYR+J4tVVBK1q0wpnv0U4JkKmqygxTDQEdfFKcfRpNRMsKx6zgzM7oLL+c4oz9A80aSs/jjp40U6bpmA46t0vgVzZpVS7TLApg3lOwe55A6ivMqE04hwcsgtCB7tJK0KxdH0pdLWlUpXylii3IVZuLm9mphsPXg6gsrqeXECtwH+Kl7jF96sLj4m6z1i773cGw1VLYCb5dEqoIKodnzgvmDVLQGtLl4B5/t7c+Q40ZwFL66bgLNmUfvmSKHr0Onsg5eT4LFp/c0vyWm1uPFwBTdBd9lTGGwvjCAF7b+Ad4b9mq9HP05TubJaXIxJ/b8f3DZU2lNU9Ivi+G2VNcL1dopLh3dt17IuC0LpHVDwuvA9TLtT21LrHm1EXlo9ly/s/4rwC5C1z00g6MvrDnK22DovCYoOJz1jpPFpsaN6412udkJndTNwdtF/zdiFF6vpMJxlNKIfD12hjQj7MiwD4qD7jkovbfcSEvtlVlTfOH3uxX+rKg3NL3B0dvFrh6I+rselNtN6F68oxk/+2araVBLuv3SZ6RvZL5q3BVi9r52bTgeUfZNwUr/G9kaoSs=""", +"clar.h" : r"""eJy9VU1P4zAQPZNfYZo9JJUFlCMLSAi1AqlCKyjavVmO4xBrEyfYztLVav874yRtmq922QOX1pnxzHvOe+O4IpIhjxAht8ubR7KaP63IHSGOC0EheS/uuEKypAg5utQmTERwEl87zq9MhIglVBFCtebKeM6RkAaxTIbCiExi5wjWGiIxVWgaiYTjaksCKJ0sVypTnVjINVMir3vZQh1nRRISGmTK+F8HOBD+WtCEaG+3Dx5/gKa9ADQe6ys8WzBUNNRl04ZobghLOJVF7pUxb1o/+tXz1MeoWmQ5fS14Q4FEulVq27oisvKVIi3uf6yeH+fk283qztnlYEvF2hSKe20VyhiRNG2h1GFNZRhk64+UbNjtKXE5WCJynNPp1EFTdFO+UlAVpZSpTKM3YWLE13kimDCotAJKudb0hcP+060xATUttCE5iEI8KFAYWZP4bR+WGR9dX6EzDGZe3C/nhNjV8v6hXE0WhWQlAUaTBEUUrBleoAlym54YzfwesN15GPhyFHe+zjkzPERRi4DJSg4HGNROPAh/PH5uwFfwXi2w0EhmBhlV8CHcjVa3MWc//0MnZus+Sagzv4/8yUoNUfgEoc78A0Mls38cp5rS0IQ9PC+Xw6PQKdp9572i+ujbirabq+3jpjt0jsZuDULfgj1SjVe6ZXvPUm7pVgyeZJEpZk0E3eA+PH2jSgr50mVfEhjwyZg7Vhxu2moYTibDl0WN9JGu36sSFBbK/hkLwtecFdZVF5MBz61+53A42nFe93SdL7OeYX3eprTNQdLHHqTxluGW4OTJlLxSoVNqWFwOg57BL8yRXZ6PXJjbT/cMi2Fg4UESgMUgsCsaELEfJPCCGQ7GQI6PIe1j+zcMFDRAwX6g3MtnOD/fmSQPIj66ukIehHcksiqm3MRZCPpZWtRKVYn05Q9fG64k2c38dTbf63eIKlZw""" +} +if __name__ == '__main__': + main() diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c new file mode 100644 index 000000000..d180285b8 --- /dev/null +++ b/tests-clar/clar_helpers.c @@ -0,0 +1,100 @@ +#include "clar_libgit2.h" +#include "posix.h" + +void clar_on_init(void) +{ + git_threads_init(); +} + +void clar_on_shutdown(void) +{ + git_threads_shutdown(); +} + +void cl_git_mkfile(const char *filename, const char *content) +{ + int fd; + + fd = p_creat(filename, 0666); + cl_assert(fd != 0); + + if (content) { + cl_must_pass(p_write(fd, content, strlen(content))); + } else { + cl_must_pass(p_write(fd, filename, strlen(filename))); + cl_must_pass(p_write(fd, "\n", 1)); + } + + cl_must_pass(p_close(fd)); +} + +void cl_git_write2file(const char *filename, const char *new_content, int flags) +{ + int fd = p_open(filename, flags, 0644); + cl_assert(fd >= 0); + if (!new_content) + new_content = "\n"; + cl_must_pass(p_write(fd, new_content, strlen(new_content))); + cl_must_pass(p_close(fd)); +} + +void cl_git_append2file(const char *filename, const char *new_content) +{ + cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_APPEND); +} + +void cl_git_rewritefile(const char *filename, const char *new_content) +{ + cl_git_write2file(filename, new_content, O_WRONLY | O_CREAT | O_TRUNC); +} + +static const char *_cl_sandbox = NULL; +static git_repository *_cl_repo = NULL; + +git_repository *cl_git_sandbox_init(const char *sandbox) +{ + /* Copy the whole sandbox folder from our fixtures to our test sandbox + * area. After this it can be accessed with `./sandbox` + */ + cl_fixture_sandbox(sandbox); + _cl_sandbox = sandbox; + + cl_git_pass(p_chdir(sandbox)); + + /* If this is not a bare repo, then rename `sandbox/.gitted` to + * `sandbox/.git` which must be done since we cannot store a folder + * named `.git` inside the fixtures folder of our libgit2 repo. + */ + if (p_access(".gitted", F_OK) == 0) + cl_git_pass(p_rename(".gitted", ".git")); + + /* If we have `gitattributes`, rename to `.gitattributes`. This may + * be necessary if we don't want the attributes to be applied in the + * libgit2 repo, but just during testing. + */ + if (p_access("gitattributes", F_OK) == 0) + cl_git_pass(p_rename("gitattributes", ".gitattributes")); + + /* As with `gitattributes`, we may need `gitignore` just for testing. */ + if (p_access("gitignore", F_OK) == 0) + cl_git_pass(p_rename("gitignore", ".gitignore")); + + cl_git_pass(p_chdir("..")); + + /* Now open the sandbox repository and make it available for tests */ + cl_git_pass(git_repository_open(&_cl_repo, sandbox)); + + return _cl_repo; +} + +void cl_git_sandbox_cleanup(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + } + if (_cl_sandbox) { + cl_fixture_cleanup(_cl_sandbox); + _cl_sandbox = NULL; + } +} diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h new file mode 100644 index 000000000..d250494f5 --- /dev/null +++ b/tests-clar/clar_libgit2.h @@ -0,0 +1,48 @@ +#ifndef __CLAR_LIBGIT2__ +#define __CLAR_LIBGIT2__ + +#include "clar.h" +#include +#include "common.h" + +/** + * Special wrapper for `clar_must_pass` that passes + * the last library error as the test failure message. + * + * Use this wrapper around all `git_` library calls that + * return error codes! + */ +#define cl_git_pass(expr) do { \ + giterr_clear(); \ + if ((expr) != 0) \ + clar__assert(0, __FILE__, __LINE__, "Function call failed: " #expr, giterr_last() ? giterr_last()->message : NULL, 1); \ + } while(0) + +/** + * Wrapper for `clar_must_fail` -- this one is + * just for consistency. Use with `git_` library + * calls that are supposed to fail! + */ +#define cl_git_fail(expr) cl_must_fail(expr) + +/* + * Some utility macros for building long strings + */ +#define REP4(STR) STR STR STR STR +#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR +#define REP16(STR) REP4(REP4(STR)) +#define REP256(STR) REP16(REP16(STR)) +#define REP1024(STR) REP4(REP256(STR)) + +/* Write the contents of a buffer to disk */ +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 mode); + +/* Git sandbox setup helpers */ + +git_repository *cl_git_sandbox_init(const char *sandbox); +void cl_git_sandbox_cleanup(void); + +#endif diff --git a/tests-clar/commit/commit.c b/tests-clar/commit/commit.c new file mode 100644 index 000000000..1205e5285 --- /dev/null +++ b/tests-clar/commit/commit.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; + +void test_commit_commit__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_commit_commit__cleanup(void) +{ + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_commit_commit__create_unexisting_update_ref(void) +{ + git_oid oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + git_reference *ref; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "some msg", tree, 1, (const git_commit **) &commit)); + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_assert(!git_oid_cmp(&oid, git_reference_oid(ref))); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); + git_reference_free(ref); +} diff --git a/tests-clar/commit/parse.c b/tests-clar/commit/parse.c new file mode 100644 index 000000000..bbb502cb5 --- /dev/null +++ b/tests-clar/commit/parse.c @@ -0,0 +1,350 @@ +#include "clar_libgit2.h" +#include +#include "commit.h" +#include "signature.h" + +// Fixture setup +static git_repository *g_repo; +void test_commit_parse__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} +void test_commit_parse__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +// Header parsing +typedef struct { + 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 } +}; + +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 } +}; + +void test_commit_parse__header(void) +{ + 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); + + 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); + + 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; +} passing_signature_test_case; + +passing_signature_test_case passing_signature_cases[] = { + {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 12345, 0}, + {"author Vicent Marti <> 12345 \n", "author ", "Vicent Marti", "", 12345, 0}, + {"author Vicent Marti 231301 +1020\n", "author ", "Vicent Marti", "tanoku@gmail.com", 231301, 620}, + {"author Vicent Marti with an outrageously long name which will probably overflow the buffer 12345 \n", "author ", "Vicent Marti with an outrageously long name which will probably overflow the buffer", "tanoku@gmail.com", 12345, 0}, + {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanokuwithaveryveryverylongemailwhichwillprobablyvoverflowtheemailbuffer@gmail.com", 12345, 0}, + {"committer Vicent Marti 123456 +0000 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, + {"committer Vicent Marti 123456 +0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 60}, + {"committer Vicent Marti 123456 -0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, -60}, + // Parse a signature without an author field + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + // Parse a signature without an author field + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + // Parse a signature with an empty author field + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + // Parse a signature with an empty email field + {"committer Vicent Marti <> 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, + // Parse a signature with an empty email field + {"committer Vicent Marti < > 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, + // Parse a signature with empty name and email + {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, + // Parse a signature with empty name and email + {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, + // Parse a signature with empty name and email + {"committer < > 123456 -0100 \n", "committer ", "", "", 123456, -60}, + // Parse an obviously invalid signature + {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, + // Parse an obviously invalid signature + {"committer foo<@bar>123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, + // Parse an obviously invalid signature + {"committer <>\n", "committer ", "", "", 0, 0}, + {"committer Vicent Marti 123456 -1500 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"committer Vicent Marti 123456 +0163 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author Vicent Marti notime \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author Vicent Marti 123456 notimezone \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author Vicent Marti notime +0100\n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author Vicent Marti \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author A U Thor , C O. Miter 1234567890 -0700\n", "author ", "A U Thor", "author@example.com", 1234567890, -420}, + {"author A U Thor and others 1234567890 -0700\n", "author ", "A U Thor", "author@example.com", 1234567890, -420}, + {"author A U Thor and others 1234567890\n", "author ", "A U Thor", "author@example.com", 1234567890, 0}, + {"author A U Thor> and others 1234567890\n", "author ", "A U Thor>", "author@example.com", 1234567890, 0}, + {NULL,NULL,NULL,NULL,0,0} +}; + +typedef struct { + const char *string; + const char *header; +} failing_signature_test_case; + +failing_signature_test_case failing_signature_cases[] = { + {"committer Vicent Marti tanoku@gmail.com> 123456 -0100 \n", "committer "}, + {"author Vicent Marti 12345 \n", "author "}, + {"author Vicent Marti 12345 \n", "committer "}, + {"author Vicent Marti 12345 \n", "author "}, + {"author Vicent Marti <\n", "committer "}, + {"author ", "author "}, + {NULL,NULL,} +}; + +void test_commit_parse__signature(void) +{ + 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 = {NULL, NULL, {0, 0}}; + cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n')); + cl_assert(strcmp(passcase->name, person.name) == 0); + cl_assert(strcmp(passcase->email, person.email) == 0); + cl_assert(passcase->time == person.when.time); + cl_assert(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 = {NULL, NULL, {0, 0}}; + cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n')); + git__free(person.name); git__free(person.email); + } +} + + + +static char *failing_commit_cases[] = { +// empty commit +"", +// random garbage +"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n", +// broken endlines 1 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\ +author Vicent Marti 1273848544 +0200\r\n\ +committer Vicent Marti 1273848544 +0200\r\n\ +\r\n\ +a test commit with broken endlines\r\n", +// broken endlines 2 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\ +author Vicent Marti 1273848544 +0200\ +committer Vicent Marti 1273848544 +0200\ +\ +another test commit with broken endlines", +// starting endlines +"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a test commit with a starting endline\n", +// corrupted commit 1 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent 05452d6349abcd67aa396df", +// corrupted commit 2 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent ", +// corrupted commit 3 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent ", +// corrupted commit 4 +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +par", +}; + + +static char *passing_commit_cases[] = { +// simple commit with no message +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n", +// simple commit, no parent +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works\n", +// simple commit, no parent, no newline in message +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works", +// simple commit, 1 parent +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent e90810b8df3e80c413d903f631643c716887138d\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works\n", +}; + +void test_commit_parse__entire_commit(void) +{ + const int broken_commit_count = sizeof(failing_commit_cases) / sizeof(*failing_commit_cases); + const int working_commit_count = sizeof(passing_commit_cases) / sizeof(*passing_commit_cases); + int i; + + for (i = 0; i < broken_commit_count; ++i) { + git_commit *commit; + commit = (git_commit*)git__malloc(sizeof(git_commit)); + memset(commit, 0x0, sizeof(git_commit)); + commit->object.repo = g_repo; + + cl_git_fail(git_commit__parse_buffer( + commit, + failing_commit_cases[i], + strlen(failing_commit_cases[i])) + ); + + git_commit__free(commit); + } + + for (i = 0; i < working_commit_count; ++i) { + git_commit *commit; + + commit = (git_commit*)git__malloc(sizeof(git_commit)); + memset(commit, 0x0, sizeof(git_commit)); + commit->object.repo = g_repo; + + cl_git_pass(git_commit__parse_buffer( + commit, + passing_commit_cases[i], + strlen(passing_commit_cases[i])) + ); + + git_commit__free(commit); + + commit = (git_commit*)git__malloc(sizeof(git_commit)); + memset(commit, 0x0, sizeof(git_commit)); + commit->object.repo = g_repo; + + cl_git_pass(git_commit__parse_buffer( + commit, + passing_commit_cases[i], + strlen(passing_commit_cases[i])) + ); + + git_commit__free(commit); + } +} + + +// 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 */ + }; + const size_t commit_count = sizeof(commit_ids) / sizeof(const char *); + unsigned int i; + + for (i = 0; i < commit_count; ++i) { + git_oid id; + git_commit *commit; + + const git_signature *author, *committer; + const char *message; + git_time_t commit_time; + unsigned int parents, p; + git_commit *parent = NULL, *old_parent = NULL; + + git_oid_fromstr(&id, commit_ids[i]); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &id)); + + message = git_commit_message(commit); + author = git_commit_author(commit); + committer = git_commit_committer(commit); + commit_time = git_commit_time(commit); + parents = git_commit_parentcount(commit); + + cl_assert(strcmp(author->name, "Scott Chacon") == 0); + cl_assert(strcmp(author->email, "schacon@gmail.com") == 0); + cl_assert(strcmp(committer->name, "Scott Chacon") == 0); + cl_assert(strcmp(committer->email, "schacon@gmail.com") == 0); + 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++) { + if (old_parent != NULL) + git_commit_free(old_parent); + + old_parent = parent; + cl_git_pass(git_commit_parent(&parent, commit, p)); + cl_assert(parent != NULL); + cl_assert(git_commit_author(parent) != NULL); // is it really a commit? + } + git_commit_free(old_parent); + git_commit_free(parent); + + cl_git_fail(git_commit_parent(&parent, commit, parents)); + git_commit_free(commit); + } +} + diff --git a/tests-clar/commit/signature.c b/tests-clar/commit/signature.c new file mode 100644 index 000000000..290b11fa3 --- /dev/null +++ b/tests-clar/commit/signature.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" + +static int try_build_signature(const char *name, const char *email, git_time_t time, int offset) +{ + git_signature *sign; + int error = 0; + + if ((error = git_signature_new(&sign, name, email, time, offset)) < 0) + return error; + + git_signature_free((git_signature *)sign); + + return error; +} + + +void test_commit_signature__create_trim(void) +{ + // creating a signature trims leading and trailing spaces + git_signature *sign; + cl_git_pass(git_signature_new(&sign, " nulltoken ", " emeric.fermas@gmail.com ", 1234567890, 60)); + cl_assert(strcmp(sign->name, "nulltoken") == 0); + cl_assert(strcmp(sign->email, "emeric.fermas@gmail.com") == 0); + git_signature_free((git_signature *)sign); +} + + +void test_commit_signature__create_empties(void) +{ + // can not create a signature with empty name or email + cl_git_pass(try_build_signature("nulltoken", "emeric.fermas@gmail.com", 1234567890, 60)); + + cl_git_fail(try_build_signature("", "emeric.fermas@gmail.com", 1234567890, 60)); + cl_git_fail(try_build_signature(" ", "emeric.fermas@gmail.com", 1234567890, 60)); + cl_git_fail(try_build_signature("nulltoken", "", 1234567890, 60)); + cl_git_fail(try_build_signature("nulltoken", " ", 1234567890, 60)); +} + +void test_commit_signature__create_one_char(void) +{ + // creating a one character signature + git_signature *sign; + cl_git_pass(git_signature_new(&sign, "x", "foo@bar.baz", 1234567890, 60)); + cl_assert(strcmp(sign->name, "x") == 0); + cl_assert(strcmp(sign->email, "foo@bar.baz") == 0); + git_signature_free((git_signature *)sign); +} + +void test_commit_signature__create_two_char(void) +{ + // creating a two character signature + git_signature *sign; + cl_git_pass(git_signature_new(&sign, "xx", "x@y.z", 1234567890, 60)); + cl_assert(strcmp(sign->name, "xx") == 0); + cl_assert(strcmp(sign->email, "x@y.z") == 0); + git_signature_free((git_signature *)sign); +} + +void test_commit_signature__create_zero_char(void) +{ + // creating a zero character signature + git_signature *sign; + cl_git_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60)); + cl_assert(sign == NULL); +} diff --git a/tests-clar/commit/write.c b/tests-clar/commit/write.c new file mode 100644 index 000000000..9c4d077a6 --- /dev/null +++ b/tests-clar/commit/write.c @@ -0,0 +1,140 @@ +#include "clar_libgit2.h" + +static const char *committer_name = "Vicent Marti"; +static const char *committer_email = "vicent@github.com"; +static const char *commit_message = "This commit has been created in memory\n\ + This is a commit created in memory and it will be written back to disk\n"; +static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; +static const char *root_commit_message = "This is a root commit\n\ + This is a root commit and should be the only one in this branch\n"; +static char *head_old; +static git_reference *head, *branch; +static git_commit *commit; + +// Fixture setup +static git_repository *g_repo; +void test_commit_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} +void test_commit_write__cleanup(void) +{ + git_reference_free(head); + git_reference_free(branch); + + git_commit_free(commit); + + git__free(head_old); + + cl_git_sandbox_cleanup(); +} + + +// write a new commit object from memory to disk +void test_commit_write__from_memory(void) +{ + git_oid tree_id, parent_id, commit_id; + git_signature *author, *committer; + const git_signature *author1, *committer1; + git_commit *parent; + git_tree *tree; + const char *commit_id_str = "8496071c1b46c854b31185ea97743be6a8774479"; + + git_oid_fromstr(&tree_id, tree_oid); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + git_oid_fromstr(&parent_id, commit_id_str); + cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + cl_git_pass(git_commit_create_v( + &commit_id, /* out id */ + g_repo, + NULL, /* do not update the HEAD */ + author, + committer, + NULL, + commit_message, + tree, + 1, parent)); + + git_object_free((git_object *)parent); + git_object_free((git_object *)tree); + + git_signature_free(committer); + git_signature_free(author); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + /* Check attributes were set correctly */ + author1 = git_commit_author(commit); + cl_assert(author1 != NULL); + cl_assert(strcmp(author1->name, committer_name) == 0); + cl_assert(strcmp(author1->email, committer_email) == 0); + cl_assert(author1->when.time == 987654321); + cl_assert(author1->when.offset == 90); + + committer1 = git_commit_committer(commit); + cl_assert(committer1 != NULL); + cl_assert(strcmp(committer1->name, committer_name) == 0); + cl_assert(strcmp(committer1->email, committer_email) == 0); + cl_assert(committer1->when.time == 123456789); + cl_assert(committer1->when.offset == 60); + + cl_assert(strcmp(git_commit_message(commit), commit_message) == 0); +} + +// create a root commit +void test_commit_write__root(void) +{ + git_oid tree_id, commit_id; + const git_oid *branch_oid; + git_signature *author, *committer; + const char *branch_name = "refs/heads/root-commit-branch"; + git_tree *tree; + + git_oid_fromstr(&tree_id, tree_oid); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + /* First we need to update HEAD so it points to our non-existant branch */ + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC); + head_old = git__strdup(git_reference_target(head)); + cl_assert(head_old != NULL); + + cl_git_pass(git_reference_set_target(head, branch_name)); + + cl_git_pass(git_commit_create_v( + &commit_id, /* out id */ + g_repo, + "HEAD", + author, + committer, + NULL, + root_commit_message, + tree, + 0)); + + git_object_free((git_object *)tree); + git_signature_free(committer); + git_signature_free(author); + + /* + * The fact that creating a commit works has already been + * tested. Here we just make sure it's our commit and that it was + * written as a root commit. + */ + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + cl_assert(git_commit_parentcount(commit) == 0); + cl_git_pass(git_reference_lookup(&branch, g_repo, branch_name)); + branch_oid = git_reference_oid(branch); + cl_git_pass(git_oid_cmp(branch_oid, &commit_id)); + cl_assert(!strcmp(git_commit_message(commit), root_commit_message)); +} diff --git a/tests-clar/config/add.c b/tests-clar/config/add.c new file mode 100644 index 000000000..9854fbb39 --- /dev/null +++ b/tests-clar/config/add.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" + +void test_config_add__initialize(void) +{ + cl_fixture_sandbox("config/config10"); +} + +void test_config_add__cleanup(void) +{ + cl_fixture_cleanup("config10"); +} + +void test_config_add__to_existing_section(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config10")); + cl_git_pass(git_config_set_int32(cfg, "empty.tmp", 5)); + cl_git_pass(git_config_get_int32(&i, cfg, "empty.tmp")); + cl_assert(i == 5); + cl_git_pass(git_config_delete(cfg, "empty.tmp")); + git_config_free(cfg); +} + +void test_config_add__to_new_section(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config10")); + cl_git_pass(git_config_set_int32(cfg, "section.tmp", 5)); + cl_git_pass(git_config_get_int32(&i, cfg, "section.tmp")); + cl_assert(i == 5); + cl_git_pass(git_config_delete(cfg, "section.tmp")); + git_config_free(cfg); +} diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c new file mode 100644 index 000000000..3b40cd09a --- /dev/null +++ b/tests-clar/config/multivar.c @@ -0,0 +1,151 @@ +#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 char *name, const char *value, void *data) +{ + int *n = (int *) data; + + GIT_UNUSED(value); + + if (!strcmp(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 char *val, void *data) +{ + int *n = (int *) data; + + GIT_UNUSED(val); + + (*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__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/config/new.c b/tests-clar/config/new.c new file mode 100644 index 000000000..fae3ce941 --- /dev/null +++ b/tests-clar/config/new.c @@ -0,0 +1,36 @@ +#include "clar_libgit2.h" + +#include "filebuf.h" +#include "fileops.h" +#include "posix.h" + +#define TEST_CONFIG "git-new-config" + +void test_config_new__write_new_config(void) +{ + const char *out; + struct git_config_file *file; + git_config *config; + + cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_set_string(config, "color.ui", "auto")); + cl_git_pass(git_config_set_string(config, "core.editor", "ed")); + + git_config_free(config); + + cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_get_string(&out, config, "color.ui")); + cl_assert_equal_s(out, "auto"); + cl_git_pass(git_config_get_string(&out, config, "core.editor")); + cl_assert_equal_s(out, "ed"); + + git_config_free(config); + + p_unlink(TEST_CONFIG); +} diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c new file mode 100644 index 000000000..f33bdd89e --- /dev/null +++ b/tests-clar/config/read.c @@ -0,0 +1,221 @@ +#include "clar_libgit2.h" + +void test_config_read__simple_read(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config0"))); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.repositoryformatversion")); + cl_assert(i == 0); + cl_git_pass(git_config_get_bool(&i, cfg, "core.filemode")); + cl_assert(i == 1); + cl_git_pass(git_config_get_bool(&i, cfg, "core.bare")); + cl_assert(i == 0); + cl_git_pass(git_config_get_bool(&i, cfg, "core.logallrefupdates")); + cl_assert(i == 1); + + git_config_free(cfg); +} + +void test_config_read__case_sensitive(void) +{ + git_config *cfg; + int i; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config1"))); + + cl_git_pass(git_config_get_string(&str, cfg, "this.that.other")); + cl_assert_equal_s(str, "true"); + cl_git_pass(git_config_get_string(&str, cfg, "this.That.other")); + cl_assert_equal_s(str, "yes"); + + cl_git_pass(git_config_get_bool(&i, cfg, "this.that.other")); + cl_assert(i == 1); + cl_git_pass(git_config_get_bool(&i, cfg, "this.That.other")); + cl_assert(i == 1); + + /* This one doesn't exist */ + cl_must_fail(git_config_get_bool(&i, cfg, "this.thaT.other")); + + git_config_free(cfg); +} + +/* + * If \ is the last non-space character on the line, we read the next + * one, separating each line with SP. + */ +void test_config_read__multiline_value(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config2"))); + + cl_git_pass(git_config_get_string(&str, cfg, "this.That.and")); + cl_assert_equal_s(str, "one one one two two three three"); + + git_config_free(cfg); +} + +/* + * This kind of subsection declaration is case-insensitive + */ +void test_config_read__subsection_header(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config3"))); + + cl_git_pass(git_config_get_string(&str, cfg, "section.subsection.var")); + cl_assert_equal_s(str, "hello"); + + /* The subsection is transformed to lower-case */ + cl_must_fail(git_config_get_string(&str, cfg, "section.subSectIon.var")); + + git_config_free(cfg); +} + +void test_config_read__lone_variable(void) +{ + git_config *cfg; + const char *str; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config4"))); + + cl_git_pass(git_config_get_string(&str, cfg, "some.section.variable")); + cl_assert(str == NULL); + + cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variable")); + cl_assert(i == 1); + + git_config_free(cfg); +} + +void test_config_read__number_suffixes(void) +{ + git_config *cfg; + int64_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config5"))); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.simple")); + cl_assert(i == 1); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.k")); + cl_assert(i == 1 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.kk")); + cl_assert(i == 1 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.m")); + cl_assert(i == 1 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.mm")); + cl_assert(i == 1 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.g")); + cl_assert(i == 1 * 1024 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.gg")); + cl_assert(i == 1 * 1024 * 1024 * 1024); + + git_config_free(cfg); +} + +void test_config_read__blank_lines(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config6"))); + + cl_git_pass(git_config_get_bool(&i, cfg, "valid.subsection.something")); + cl_assert(i == 1); + + cl_git_pass(git_config_get_bool(&i, cfg, "something.else.something")); + cl_assert(i == 0); + + git_config_free(cfg); +} + +void test_config_read__invalid_ext_headers(void) +{ + git_config *cfg; + cl_must_fail(git_config_open_ondisk(&cfg, cl_fixture("config/config7"))); +} + +void test_config_read__empty_files(void) +{ + git_config *cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config8"))); + git_config_free(cfg); +} + +void test_config_read__header_in_last_line(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config10"))); + git_config_free(cfg); +} + +void test_config_read__prefixes(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + cl_git_pass(git_config_get_string(&str, cfg, "remote.ab.url")); + cl_assert_equal_s(str, "http://example.com/git/ab"); + + cl_git_pass(git_config_get_string(&str, cfg, "remote.abba.url")); + cl_assert_equal_s(str, "http://example.com/git/abba"); + + git_config_free(cfg); +} + +void test_config_read__escaping_quotes(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config13"))); + cl_git_pass(git_config_get_string(&str, cfg, "core.editor")); + cl_assert(strcmp(str, "\"C:/Program Files/Nonsense/bah.exe\" \"--some option\"") == 0); + + git_config_free(cfg); +} + +#if 0 + +BEGIN_TEST(config10, "a repo's config overrides the global config") + git_repository *repo; + git_config *cfg; + int32_t version; + + cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + cl_git_pass(git_repository_config(&cfg, repo, GLOBAL_CONFIG, NULL)); + cl_git_pass(git_config_get_int32(cfg, "core.repositoryformatversion", &version)); + cl_assert(version == 0); + git_config_free(cfg); + git_repository_free(repo); +END_TEST + +BEGIN_TEST(config11, "fall back to the global config") + git_repository *repo; + git_config *cfg; + int32_t num; + + cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + cl_git_pass(git_repository_config(&cfg, repo, GLOBAL_CONFIG, NULL)); + cl_git_pass(git_config_get_int32(cfg, "core.something", &num)); + cl_assert(num == 2); + git_config_free(cfg); + git_repository_free(repo); +END_TEST +#endif diff --git a/tests-clar/config/stress.c b/tests-clar/config/stress.c new file mode 100644 index 000000000..3de1f7692 --- /dev/null +++ b/tests-clar/config/stress.c @@ -0,0 +1,61 @@ +#include "clar_libgit2.h" + +#include "filebuf.h" +#include "fileops.h" +#include "posix.h" + +void test_config_stress__initialize(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + + cl_git_pass(git_filebuf_open(&file, "git-test-config", 0)); + + 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)); +} + +void test_config_stress__cleanup(void) +{ + p_unlink("git-test-config"); +} + +void test_config_stress__dont_break_on_invalid_input(void) +{ + const char *editor, *color; + struct git_config_file *file; + git_config *config; + + cl_assert(git_path_exists("git-test-config")); + cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_get_string(&color, config, "color.ui")); + cl_git_pass(git_config_get_string(&editor, config, "core.editor")); + + git_config_free(config); +} + +void test_config_stress__comments(void) +{ + struct git_config_file *file; + git_config *config; + const char *str; + + cl_git_pass(git_config_file__ondisk(&file, cl_fixture("config/config12"))); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_get_string(&str, config, "some.section.other")); + cl_assert(!strcmp(str, "hello! \" ; ; ; ")); + + cl_git_pass(git_config_get_string(&str, config, "some.section.multi")); + cl_assert(!strcmp(str, "hi, this is a ; multiline comment # with ;\n special chars and other stuff !@#")); + + cl_git_pass(git_config_get_string(&str, config, "some.section.back")); + cl_assert(!strcmp(str, "this is \ba phrase")); + + git_config_free(config); +} diff --git a/tests-clar/config/write.c b/tests-clar/config/write.c new file mode 100644 index 000000000..f8774473e --- /dev/null +++ b/tests-clar/config/write.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" + +void test_config_write__initialize(void) +{ + cl_fixture_sandbox("config/config9"); +} + +void test_config_write__cleanup(void) +{ + cl_fixture_cleanup("config9"); +} + +void test_config_write__replace_value(void) +{ + git_config *cfg; + int i; + int64_t l, expected = +9223372036854775803; + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy")); + cl_assert(i == 5); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int64(cfg, "core.verylong", expected)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_int64(&l, cfg, "core.verylong")); + cl_assert(l == expected); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_must_fail(git_config_get_int32(&i, cfg, "core.verylong")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int64(cfg, "core.verylong", 1)); + git_config_free(cfg); +} + +void test_config_write__delete_value(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_delete(cfg, "core.dummy")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_assert(git_config_get_int32(&i, cfg, "core.dummy") == GIT_ENOTFOUND); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); + git_config_free(cfg); +} + +void test_config_write__write_subsection(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_string(cfg, "my.own.var", "works")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_string(&str, cfg, "my.own.var")); + cl_git_pass(strcmp(str, "works")); + git_config_free(cfg); +} + +void test_config_write__delete_inexistent(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_assert(git_config_delete(cfg, "core.imaginary") == GIT_ENOTFOUND); + git_config_free(cfg); +} diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c new file mode 100644 index 000000000..6a718f459 --- /dev/null +++ b/tests-clar/core/buffer.c @@ -0,0 +1,613 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +#define TESTSTR "Have you seen that? Have you seeeen that??" +const char *test_string = TESTSTR; +const char *test_string_x2 = TESTSTR TESTSTR; + +#define TESTSTR_4096 REP1024("1234") +#define TESTSTR_8192 REP1024("12341234") +const char *test_4096 = TESTSTR_4096; +const char *test_8192 = TESTSTR_8192; + +/* test basic data concatenation */ +void test_core_buffer__0(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_assert(buf.size == 0); + + git_buf_puts(&buf, test_string); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_buf_cstr(&buf)); + + git_buf_puts(&buf, test_string); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +/* test git_buf_printf */ +void test_core_buffer__1(void) +{ + git_buf buf = GIT_BUF_INIT; + + git_buf_printf(&buf, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 ", git_buf_cstr(&buf)); + + git_buf_printf(&buf, "%s %d", "woop", 42); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 woop 42", git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +/* more thorough test of concatenation options */ +void test_core_buffer__2(void) +{ + git_buf buf = GIT_BUF_INIT; + int i; + char data[128]; + + cl_assert(buf.size == 0); + + /* this must be safe to do */ + git_buf_free(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* empty buffer should be empty string */ + cl_assert_equal_s("", git_buf_cstr(&buf)); + cl_assert(buf.size == 0); + /* cl_assert(buf.asize == 0); -- should not assume what git_buf does */ + + /* free should set us back to the beginning */ + git_buf_free(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* add letter */ + git_buf_putc(&buf, '+'); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("+", git_buf_cstr(&buf)); + + /* add letter again */ + git_buf_putc(&buf, '+'); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("++", git_buf_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_buf_putc(&buf, '+'); + cl_assert(git_buf_oom(&buf) == 0); + } + cl_assert_equal_s("++++++++++++++++++", git_buf_cstr(&buf)); + + git_buf_free(&buf); + + /* add data */ + git_buf_put(&buf, "xo", 2); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("xo", git_buf_cstr(&buf)); + + /* add letter again */ + git_buf_put(&buf, "xo", 2); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s("xoxo", git_buf_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_buf_put(&buf, "xo", 2); + cl_assert(git_buf_oom(&buf) == 0); + } + cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo", + git_buf_cstr(&buf)); + + git_buf_free(&buf); + + /* set to string */ + git_buf_sets(&buf, test_string); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_buf_cstr(&buf)); + + /* append string */ + git_buf_puts(&buf, test_string); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_buf_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_buf_sets(&buf, test_string); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_buf_cstr(&buf)); + + /* test clear */ + git_buf_clear(&buf); + cl_assert_equal_s("", git_buf_cstr(&buf)); + + git_buf_free(&buf); + + /* test extracting data into buffer */ + git_buf_puts(&buf, REP4("0123456789")); + cl_assert(git_buf_oom(&buf) == 0); + + git_buf_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + git_buf_copy_cstr(data, 11, &buf); + cl_assert_equal_s("0123456789", data); + git_buf_copy_cstr(data, 3, &buf); + cl_assert_equal_s("01", data); + git_buf_copy_cstr(data, 1, &buf); + cl_assert_equal_s("", data); + + git_buf_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + + git_buf_sets(&buf, REP256("x")); + git_buf_copy_cstr(data, sizeof(data), &buf); + /* since sizeof(data) == 128, only 127 bytes should be copied */ + cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x") + REP16("x") "xxxxxxxxxxxxxxx", data); + + git_buf_free(&buf); + + git_buf_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s("", data); +} + +/* let's do some tests with larger buffers to push our limits */ +void test_core_buffer__3(void) +{ + git_buf buf = GIT_BUF_INIT; + + /* set to string */ + git_buf_set(&buf, test_4096, 4096); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_buf_cstr(&buf)); + + /* append string */ + git_buf_puts(&buf, test_4096); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_8192, git_buf_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_buf_set(&buf, test_4096, 4096); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +/* let's try some producer/consumer tests */ +void test_core_buffer__4(void) +{ + git_buf buf = GIT_BUF_INIT; + int i; + + for (i = 0; i < 10; ++i) { + git_buf_puts(&buf, "1234"); /* add 4 */ + cl_assert(git_buf_oom(&buf) == 0); + git_buf_consume(&buf, buf.ptr + 2); /* eat the first two */ + cl_assert(strlen(git_buf_cstr(&buf)) == (size_t)((i + 1) * 2)); + } + /* we have appended 1234 10x and removed the first 20 letters */ + cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf)); + + git_buf_consume(&buf, NULL); + cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf)); + + git_buf_consume(&buf, "invalid pointer"); + cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf)); + + git_buf_consume(&buf, buf.ptr); + cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf)); + + git_buf_consume(&buf, buf.ptr + 1); + cl_assert_equal_s("2341234123412341234", git_buf_cstr(&buf)); + + git_buf_consume(&buf, buf.ptr + buf.size); + cl_assert_equal_s("", git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + + +static void +check_buf_append( + const char* data_a, + const char* data_b, + const char* expected_data, + size_t expected_size, + size_t expected_asize) +{ + git_buf tgt = GIT_BUF_INIT; + + git_buf_sets(&tgt, data_a); + cl_assert(git_buf_oom(&tgt) == 0); + git_buf_puts(&tgt, data_b); + cl_assert(git_buf_oom(&tgt) == 0); + cl_assert_equal_s(expected_data, git_buf_cstr(&tgt)); + cl_assert(tgt.size == expected_size); + if (expected_asize > 0) + cl_assert(tgt.asize == expected_asize); + + git_buf_free(&tgt); +} + +static void +check_buf_append_abc( + const char* buf_a, + const char* buf_b, + const char* buf_c, + const char* expected_ab, + const char* expected_abc, + const char* expected_abca, + const char* expected_abcab, + const char* expected_abcabc) +{ + git_buf buf = GIT_BUF_INIT; + + git_buf_sets(&buf, buf_a); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(buf_a, git_buf_cstr(&buf)); + + git_buf_puts(&buf, buf_b); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected_ab, git_buf_cstr(&buf)); + + git_buf_puts(&buf, buf_c); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected_abc, git_buf_cstr(&buf)); + + git_buf_puts(&buf, buf_a); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected_abca, git_buf_cstr(&buf)); + + git_buf_puts(&buf, buf_b); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected_abcab, git_buf_cstr(&buf)); + + git_buf_puts(&buf, buf_c); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected_abcabc, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +/* more variations on append tests */ +void test_core_buffer__5(void) +{ + check_buf_append("", "", "", 0, 8); + check_buf_append("a", "", "a", 1, 8); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("a", "", "a", 1, 8); + check_buf_append("a", "b", "ab", 2, 8); + check_buf_append("", "abcdefgh", "abcdefgh", 8, 16); + check_buf_append("abcdefgh", "", "abcdefgh", 8, 16); + + /* buffer with starting asize will grow to: + * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9, + * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18, + * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27, + * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36, + * ... + * follow sequence until value > target size, + * then round up to nearest multiple of 8. + */ + + check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16); + check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16); + check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24); + check_buf_append("0123456789", "0123456789", + "01234567890123456789", 20, 24); + check_buf_append(REP16("x"), REP16("o"), + REP16("x") REP16("o"), 32, 40); + + check_buf_append(test_4096, "", test_4096, 4096, 4104); + check_buf_append(test_4096, test_4096, test_8192, 8192, 9240); + + /* check sequences of appends */ + check_buf_append_abc("a", "b", "c", + "ab", "abc", "abca", "abcab", "abcabc"); + check_buf_append_abc("a1", "b2", "c3", + "a1b2", "a1b2c3", "a1b2c3a1", + "a1b2c3a1b2", "a1b2c3a1b2c3"); + check_buf_append_abc("a1/", "b2/", "c3/", + "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/", + "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/"); +} + +/* test swap */ +void test_core_buffer__6(void) +{ + git_buf a = GIT_BUF_INIT; + git_buf b = GIT_BUF_INIT; + + git_buf_sets(&a, "foo"); + cl_assert(git_buf_oom(&a) == 0); + git_buf_sets(&b, "bar"); + cl_assert(git_buf_oom(&b) == 0); + + cl_assert_equal_s("foo", git_buf_cstr(&a)); + cl_assert_equal_s("bar", git_buf_cstr(&b)); + + git_buf_swap(&a, &b); + + cl_assert_equal_s("bar", git_buf_cstr(&a)); + cl_assert_equal_s("foo", git_buf_cstr(&b)); + + git_buf_free(&a); + git_buf_free(&b); +} + + +/* test detach/attach data */ +void test_core_buffer__7(void) +{ + const char *fun = "This is fun"; + git_buf a = GIT_BUF_INIT; + char *b = NULL; + + git_buf_sets(&a, "foo"); + cl_assert(git_buf_oom(&a) == 0); + cl_assert_equal_s("foo", git_buf_cstr(&a)); + + b = git_buf_detach(&a); + + cl_assert_equal_s("foo", b); + cl_assert_equal_s("", a.ptr); + git__free(b); + + b = git_buf_detach(&a); + + cl_assert_equal_s(NULL, b); + cl_assert_equal_s("", a.ptr); + + git_buf_free(&a); + + b = git__strdup(fun); + git_buf_attach(&a, b, 0); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_buf_free(&a); + + b = git__strdup(fun); + git_buf_attach(&a, b, strlen(fun) + 1); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_buf_free(&a); +} + + +static void +check_joinbuf_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_buf buf = GIT_BUF_INIT; + + git_buf_join(&buf, sep, a, b); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + git_buf_free(&buf); +} + +static void +check_joinbuf_n_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_buf buf = GIT_BUF_INIT; + + git_buf_sets(&buf, a); + cl_assert(git_buf_oom(&buf) == 0); + + git_buf_join_n(&buf, sep, 1, b); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +static void +check_joinbuf_n_4( + const char *a, + const char *b, + const char *c, + const char *d, + const char *expected) +{ + char sep = ';'; + git_buf buf = GIT_BUF_INIT; + git_buf_join_n(&buf, sep, 4, a, b, c, d); + cl_assert(git_buf_oom(&buf) == 0); + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + git_buf_free(&buf); +} + +/* test join */ +void test_core_buffer__8(void) +{ + git_buf a = GIT_BUF_INIT; + + git_buf_join_n(&a, '/', 1, "foo"); + cl_assert(git_buf_oom(&a) == 0); + cl_assert_equal_s("foo", git_buf_cstr(&a)); + + git_buf_join_n(&a, '/', 1, "bar"); + cl_assert(git_buf_oom(&a) == 0); + cl_assert_equal_s("foo/bar", git_buf_cstr(&a)); + + git_buf_join_n(&a, '/', 1, "baz"); + cl_assert(git_buf_oom(&a) == 0); + cl_assert_equal_s("foo/bar/baz", git_buf_cstr(&a)); + + git_buf_free(&a); + + check_joinbuf_2("", "", ""); + check_joinbuf_2("", "a", "a"); + check_joinbuf_2("", "/a", "/a"); + check_joinbuf_2("a", "", "a/"); + check_joinbuf_2("a", "/", "a/"); + check_joinbuf_2("a", "b", "a/b"); + check_joinbuf_2("/", "a", "/a"); + check_joinbuf_2("/", "", "/"); + check_joinbuf_2("/a", "/b", "/a/b"); + check_joinbuf_2("/a", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "b/", "/a/b/"); + check_joinbuf_2("/a/", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "//b/", "/a/b/"); + check_joinbuf_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_n_2("", "", ""); + check_joinbuf_n_2("", "a", "a"); + check_joinbuf_n_2("", "/a", "/a"); + check_joinbuf_n_2("a", "", "a/"); + check_joinbuf_n_2("a", "/", "a/"); + check_joinbuf_n_2("a", "b", "a/b"); + check_joinbuf_n_2("/", "a", "/a"); + check_joinbuf_n_2("/", "", "/"); + check_joinbuf_n_2("/a", "/b", "/a/b"); + check_joinbuf_n_2("/a", "/b/", "/a/b/"); + check_joinbuf_n_2("/a/", "b/", "/a/b/"); + check_joinbuf_n_2("/a/", "/b/", "/a/b/"); + check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_n_4("", "", "", "", ""); + check_joinbuf_n_4("", "a", "", "", "a;"); + check_joinbuf_n_4("a", "", "", "", "a;"); + check_joinbuf_n_4("", "", "", "a", "a"); + check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;"); + check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d"); + check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop"); + check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;"); + check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;"); +} + +void test_core_buffer__9(void) +{ + git_buf buf = GIT_BUF_INIT; + + /* just some exhaustive tests of various separator placement */ + char *a[] = { "", "-", "a-", "-a", "-a-" }; + char *b[] = { "", "-", "b-", "-b", "-b-" }; + char sep[] = { 0, '-', '/' }; + char *expect_null[] = { "", "-", "a-", "-a", "-a-", + "-", "--", "a--", "-a-", "-a--", + "b-", "-b-", "a-b-", "-ab-", "-a-b-", + "-b", "--b", "a--b", "-a-b", "-a--b", + "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" }; + char *expect_dash[] = { "", "-", "a-", "-a-", "-a-", + "-", "-", "a-", "-a-", "-a-", + "b-", "-b-", "a-b-", "-a-b-", "-a-b-", + "-b", "-b", "a-b", "-a-b", "-a-b", + "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" }; + char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/", + "-", "-/-", "a-/-", "-a/-", "-a-/-", + "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-", + "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b", + "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" }; + char **expect_values[] = { expect_null, expect_dash, expect_slas }; + char separator, **expect; + unsigned int s, i, j; + + for (s = 0; s < sizeof(sep) / sizeof(char); ++s) { + separator = sep[s]; + expect = expect_values[s]; + + for (j = 0; j < sizeof(b) / sizeof(char*); ++j) { + for (i = 0; i < sizeof(a) / sizeof(char*); ++i) { + git_buf_join(&buf, separator, a[i], b[j]); + cl_assert_equal_s(*expect, buf.ptr); + expect++; + } + } + } + + git_buf_free(&buf); +} + +void test_core_buffer__10(void) +{ + git_buf a = GIT_BUF_INIT; + + cl_git_pass(git_buf_join_n(&a, '/', 1, "test")); + cl_assert_equal_s(a.ptr, "test"); + cl_git_pass(git_buf_join_n(&a, '/', 1, "string")); + cl_assert_equal_s(a.ptr, "test/string"); + git_buf_clear(&a); + cl_git_pass(git_buf_join_n(&a, '/', 3, "test", "string", "join")); + cl_assert_equal_s(a.ptr, "test/string/join"); + cl_git_pass(git_buf_join_n(&a, '/', 2, a.ptr, "more")); + cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more"); + + git_buf_free(&a); +} + +void test_core_buffer__11(void) +{ + git_buf a = GIT_BUF_INIT; + git_strarray t; + char *t1[] = { "nothing", "in", "common" }; + char *t2[] = { "something", "something else", "some other" }; + char *t3[] = { "something", "some fun", "no fun" }; + char *t4[] = { "happy", "happier", "happiest" }; + char *t5[] = { "happiest", "happier", "happy" }; + char *t6[] = { "no", "nope", "" }; + char *t7[] = { "", "doesn't matter" }; + + t.strings = t1; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, ""); + + t.strings = t2; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, "some"); + + t.strings = t3; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, ""); + + t.strings = t4; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, "happ"); + + t.strings = t5; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, "happ"); + + t.strings = t6; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, ""); + + t.strings = t7; + t.count = 3; + cl_git_pass(git_buf_common_prefix(&a, &t)); + cl_assert_equal_s(a.ptr, ""); + + git_buf_free(&a); +} diff --git a/tests-clar/core/dirent.c b/tests-clar/core/dirent.c new file mode 100644 index 000000000..5a7859d1b --- /dev/null +++ b/tests-clar/core/dirent.c @@ -0,0 +1,235 @@ +#include "clar_libgit2.h" +#include "fileops.h" + +typedef struct name_data { + int count; /* return count */ + char *name; /* filename */ +} name_data; + +typedef struct walk_data { + char *sub; /* sub-directory name */ + name_data *names; /* name state data */ + git_buf path; +} walk_data; + + +static char *top_dir = "dir-walk"; +static walk_data *state_loc; + +static void setup(walk_data *d) +{ + name_data *n; + + cl_must_pass(p_mkdir(top_dir, 0777)); + + cl_must_pass(p_chdir(top_dir)); + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_mkdir(d->sub, 0777)); + + cl_git_pass(git_buf_sets(&d->path, d->sub)); + + state_loc = d; + + for (n = d->names; n->name; n++) { + git_file fd = p_creat(n->name, 0666); + cl_assert(fd >= 0); + p_close(fd); + n->count = 0; + } +} + +static void dirent_cleanup__cb(void *_d) +{ + walk_data *d = _d; + name_data *n; + + for (n = d->names; n->name; n++) { + cl_must_pass(p_unlink(n->name)); + } + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_rmdir(d->sub)); + + cl_must_pass(p_chdir("..")); + + cl_must_pass(p_rmdir(top_dir)); + + git_buf_free(&d->path); +} + +static void check_counts(walk_data *d) +{ + name_data *n; + + for (n = d->names; n->name; n++) { + cl_assert(n->count == 1); + } +} + +static int one_entry(void *state, git_buf *path) +{ + walk_data *d = (walk_data *) state; + name_data *n; + + if (state != state_loc) + return GIT_ERROR; + + if (path != &d->path) + return GIT_ERROR; + + for (n = d->names; n->name; n++) { + if (!strcmp(n->name, path->ptr)) { + n->count++; + return 0; + } + } + + 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" }, + { 0, "./asdf" }, + { 0, "./pack-foo.pack" }, + { 0, NULL } +}; +static walk_data dot = { + ".", + dot_names, + GIT_BUF_INIT +}; + +/* make sure that the '.' folder is not traversed */ +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)); + + check_counts(&dot); +} + + +static name_data sub_names[] = { + { 0, "sub/a" }, + { 0, "sub/asdf" }, + { 0, "sub/pack-foo.pack" }, + { 0, NULL } +}; +static walk_data sub = { + "sub", + sub_names, + GIT_BUF_INIT +}; + +/* traverse a subfolder */ +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)); + + check_counts(&sub); +} + + +static walk_data sub_slash = { + "sub/", + sub_names, + GIT_BUF_INIT +}; + +/* traverse a slash-terminated subfolder */ +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)); + + check_counts(&sub_slash); +} + + +static name_data empty_names[] = { + { 0, NULL } +}; +static walk_data empty = { + "empty", + empty_names, + GIT_BUF_INIT +}; + +/* make sure that empty folders are not traversed */ +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)); + + check_counts(&empty); + + /* make sure callback not called */ + cl_git_pass(git_path_direach(&empty.path, + dont_call_me, + &empty)); +} + +static name_data odd_names[] = { + { 0, "odd/.a" }, + { 0, "odd/..c" }, + /* the following don't work on cygwin/win32 */ + /* { 0, "odd/.b." }, */ + /* { 0, "odd/..d.." }, */ + { 0, NULL } +}; +static walk_data odd = { + "odd", + odd_names, + GIT_BUF_INIT +}; + +/* make sure that strange looking filenames ('..c') are traversed */ +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)); + + check_counts(&odd); +} + +/* test filename length limits */ +void test_core_dirent__length_limits(void) +{ + char *big_filename = (char *)git__malloc(FILENAME_MAX + 1); + memset(big_filename, 'a', FILENAME_MAX + 1); + big_filename[FILENAME_MAX] = 0; + + cl_must_fail(p_creat(big_filename, 0666)); + git__free(big_filename); +} diff --git a/tests-clar/core/env.c b/tests-clar/core/env.c new file mode 100644 index 000000000..15d431f01 --- /dev/null +++ b/tests-clar/core/env.c @@ -0,0 +1,168 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "path.h" + +#ifdef GIT_WIN32 + +#include "win32/utf-conv.h" + +static char *cl_getenv(const char *name) +{ + wchar_t *name_utf16 = gitwin_to_utf16(name); + DWORD value_len, alloc_len; + wchar_t *value_utf16; + char *value_utf8; + + cl_assert(name_utf16); + alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); + if (alloc_len <= 0) + return NULL; + + cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); + + value_len = GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); + cl_assert_equal_i(value_len, alloc_len - 1); + + cl_assert(value_utf8 = gitwin_from_utf16(value_utf16)); + + git__free(value_utf16); + + return value_utf8; +} + +static int cl_setenv(const char *name, const char *value) +{ + wchar_t *name_utf16 = gitwin_to_utf16(name); + wchar_t *value_utf16 = value ? gitwin_to_utf16(value) : NULL; + + cl_assert(name_utf16); + cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); + + git__free(name_utf16); + git__free(value_utf16); + + return 0; + +} +#else + +#include +#define cl_getenv(n) getenv(n) + +static int cl_setenv(const char *name, const char *value) +{ + return (value == NULL) ? unsetenv(name) : setenv(name, value, 1); +} +#endif + +#ifdef GIT_WIN32 +static char *env_userprofile = NULL; +static char *env_programfiles = NULL; +#else +static char *env_home = NULL; +#endif + +void test_core_env__initialize(void) +{ +#ifdef GIT_WIN32 + env_userprofile = cl_getenv("USERPROFILE"); + env_programfiles = cl_getenv("PROGRAMFILES"); +#else + env_home = cl_getenv("HOME"); +#endif +} + +void test_core_env__cleanup(void) +{ +#ifdef GIT_WIN32 + cl_setenv("USERPROFILE", env_userprofile); + git__free(env_userprofile); + cl_setenv("PROGRAMFILES", env_programfiles); + git__free(env_programfiles); +#else + cl_setenv("HOME", env_home); +#endif +} + +void test_core_env__0(void) +{ + static char *home_values[] = { + "fake_home", + "fáke_hõme", /* all in latin-1 supplement */ + "fĀke_Ĥome", /* latin extended */ + "fακε_hοmέ", /* having fun with greek */ + "faงe_นome", /* now I have no idea, but thai characters */ + "f\xe1\x9cx80ke_\xe1\x9c\x91ome", /* tagalog characters */ + "\xe1\xb8\x9fẢke_hoṁe", /* latin extended additional */ + "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ + NULL + }; + git_buf path = GIT_BUF_INIT, found = GIT_BUF_INIT; + char **val; + char *check; + + for (val = home_values; *val != NULL; val++) { + + if (p_mkdir(*val, 0777) == 0) { + /* if we can't make the directory, let's just assume + * we are on a filesystem that doesn't support the + * characters in question and skip this test... + */ + cl_git_pass(git_path_prettify(&path, *val, NULL)); + +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("USERPROFILE", path.ptr)); + + /* do a quick check that it was set correctly */ + check = cl_getenv("USERPROFILE"); + cl_assert_equal_s(path.ptr, check); + git__free(check); +#else + cl_git_pass(cl_setenv("HOME", path.ptr)); + + /* do a quick check that it was set correctly */ + check = cl_getenv("HOME"); + cl_assert_equal_s(path.ptr, check); +#endif + + cl_git_pass(git_buf_puts(&path, "/testfile")); + cl_git_mkfile(path.ptr, "find me"); + + cl_git_pass(git_futils_find_global_file(&found, "testfile")); + } + } + + git_buf_free(&path); + git_buf_free(&found); +} + +void test_core_env__1(void) +{ + git_buf path = GIT_BUF_INIT; + + cl_assert(git_futils_find_global_file(&path, "nonexistentfile") == GIT_ENOTFOUND); + +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); +#else + cl_git_pass(cl_setenv("HOME", "doesnotexist")); +#endif + + cl_assert(git_futils_find_global_file(&path, "nonexistentfile") == GIT_ENOTFOUND); + +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("USERPROFILE", NULL)); +#else + cl_git_pass(cl_setenv("HOME", NULL)); +#endif + + cl_assert(git_futils_find_global_file(&path, "nonexistentfile") == -1); + + cl_assert(git_futils_find_system_file(&path, "nonexistentfile") == GIT_ENOTFOUND); + +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); + + cl_assert(git_futils_find_system_file(&path, "nonexistentfile") == -1); +#endif +} diff --git a/tests-clar/core/errors.c b/tests-clar/core/errors.c new file mode 100644 index 000000000..10c0cdd3f --- /dev/null +++ b/tests-clar/core/errors.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "common.h" +#include "util.h" +#include "posix.h" + +void test_core_errors__new_school(void) +{ + char *str_in_error; + + giterr_clear(); + cl_assert(giterr_last() == NULL); + + giterr_set_oom(); /* internal fn */ + + cl_assert(giterr_last() != NULL); + cl_assert(giterr_last()->klass == GITERR_NOMEMORY); + str_in_error = strstr(giterr_last()->message, "memory"); + cl_assert(str_in_error != NULL); + + giterr_clear(); + + giterr_set(GITERR_REPOSITORY, "This is a test"); /* internal fn */ + + cl_assert(giterr_last() != NULL); + str_in_error = strstr(giterr_last()->message, "This is a test"); + cl_assert(str_in_error != NULL); + + giterr_clear(); + cl_assert(giterr_last() == NULL); + + do { + struct stat st; + memset(&st, 0, sizeof(st)); + cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); + GIT_UNUSED(st); + } while (false); + giterr_set(GITERR_OS, "stat failed"); /* internal fn */ + + cl_assert(giterr_last() != NULL); + str_in_error = strstr(giterr_last()->message, "stat failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("stat failed: ")); + +#ifdef GIT_WIN32 + giterr_clear(); + + /* The MSDN docs use this to generate a sample error */ + cl_assert(GetProcessId(NULL) == 0); + giterr_set(GITERR_OS, "GetProcessId failed"); /* internal fn */ + + cl_assert(giterr_last() != NULL); + str_in_error = strstr(giterr_last()->message, "GetProcessId failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: ")); +#endif + + giterr_clear(); +} diff --git a/tests-clar/core/filebuf.c b/tests-clar/core/filebuf.c new file mode 100644 index 000000000..eab8a26eb --- /dev/null +++ b/tests-clar/core/filebuf.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "filebuf.h" + +/* make sure git_filebuf_open doesn't delete an existing lock */ +void test_core_filebuf__0(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + int fd; + char test[] = "test", testlock[] = "test.lock"; + + fd = p_creat(testlock, 0744); + + cl_must_pass(fd); + cl_must_pass(p_close(fd)); + + cl_git_fail(git_filebuf_open(&file, test, 0)); + cl_assert(git_path_exists(testlock)); + + cl_must_pass(p_unlink(testlock)); +} + + +/* make sure GIT_FILEBUF_APPEND works as expected */ +void test_core_filebuf__1(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + int fd; + char test[] = "test"; + + fd = p_creat(test, 0666); + cl_must_pass(fd); + cl_must_pass(p_write(fd, "libgit2 rocks\n", 14)); + cl_must_pass(p_close(fd)); + + cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_git_pass(git_filebuf_commit(&file, 0666)); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_write writes large buffer correctly */ +void test_core_filebuf__2(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */ + + memset(buf, 0xfe, sizeof(buf)); + + cl_git_pass(git_filebuf_open(&file, test, 0)); + cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); + cl_git_pass(git_filebuf_commit(&file, 0666)); + + cl_must_pass(p_unlink(test)); +} + +/* make sure git_filebuf_cleanup clears the buffer */ +void test_core_filebuf__4(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0)); + cl_assert(file.buffer != NULL); + + git_filebuf_cleanup(&file); + cl_assert(file.buffer == NULL); +} + + +/* make sure git_filebuf_commit clears the buffer */ +void test_core_filebuf__5(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0)); + 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_assert(file.buffer == NULL); + + cl_must_pass(p_unlink(test)); +} diff --git a/tests-clar/core/hex.c b/tests-clar/core/hex.c new file mode 100644 index 000000000..930af1670 --- /dev/null +++ b/tests-clar/core/hex.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" +#include "util.h" + +void test_core_hex__fromhex(void) +{ + /* Passing cases */ + cl_assert(git__fromhex('0') == 0x0); + cl_assert(git__fromhex('1') == 0x1); + cl_assert(git__fromhex('3') == 0x3); + cl_assert(git__fromhex('9') == 0x9); + cl_assert(git__fromhex('A') == 0xa); + cl_assert(git__fromhex('C') == 0xc); + cl_assert(git__fromhex('F') == 0xf); + cl_assert(git__fromhex('a') == 0xa); + cl_assert(git__fromhex('c') == 0xc); + cl_assert(git__fromhex('f') == 0xf); + + /* Failing cases */ + cl_assert(git__fromhex('g') == -1); + cl_assert(git__fromhex('z') == -1); + cl_assert(git__fromhex('X') == -1); +} diff --git a/tests-clar/core/oid.c b/tests-clar/core/oid.c new file mode 100644 index 000000000..c89713955 --- /dev/null +++ b/tests-clar/core/oid.c @@ -0,0 +1,18 @@ +#include "clar_libgit2.h" + +static git_oid id; +const char *str_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; + +void test_core_oid__initialize(void) +{ + cl_git_pass(git_oid_fromstr(&id, str_oid)); +} + +void test_core_oid__streq(void) +{ + cl_assert(git_oid_streq(&id, str_oid) == 0); + cl_assert(git_oid_streq(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") == -1); + + cl_assert(git_oid_streq(&id, "deadbeef") == -1); + cl_assert(git_oid_streq(&id, "I'm not an oid.... :)") == -1); +} diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c new file mode 100644 index 000000000..d826612ac --- /dev/null +++ b/tests-clar/core/path.c @@ -0,0 +1,420 @@ +#include "clar_libgit2.h" +#include + +static void +check_dirname(const char *A, const char *B) +{ + git_buf dir = GIT_BUF_INIT; + char *dir2; + + cl_assert(git_path_dirname_r(&dir, A) >= 0); + cl_assert_equal_s(B, dir.ptr); + git_buf_free(&dir); + + cl_assert((dir2 = git_path_dirname(A)) != NULL); + cl_assert_equal_s(B, dir2); + git__free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + git_buf base = GIT_BUF_INIT; + char *base2; + + cl_assert(git_path_basename_r(&base, A) >= 0); + cl_assert_equal_s(B, base.ptr); + git_buf_free(&base); + + cl_assert((base2 = git_path_basename(A)) != NULL); + cl_assert_equal_s(B, base2); + git__free(base2); +} + +static void +check_topdir(const char *A, const char *B) +{ + const char *dir; + + cl_assert((dir = git_path_topdir(A)) != NULL); + cl_assert_equal_s(B, dir); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + git_buf joined_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&joined_path, path_a, path_b)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_buf_free(&joined_path); +} + +static void +check_joinpath_n( + const char *path_a, + const char *path_b, + const char *path_c, + const char *path_d, + const char *expected_path) +{ + git_buf joined_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_join_n(&joined_path, '/', 4, + path_a, path_b, path_c, path_d)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_buf_free(&joined_path); +} + + +/* get the dirname of a path */ +void test_core_path__00_dirname(void) +{ + check_dirname(NULL, "."); + check_dirname("", "."); + check_dirname("a", "."); + check_dirname("/", "/"); + check_dirname("/usr", "/"); + check_dirname("/usr/", "/"); + check_dirname("/usr/lib", "/usr"); + check_dirname("/usr/lib/", "/usr"); + check_dirname("/usr/lib//", "/usr"); + check_dirname("usr/lib", "usr"); + check_dirname("usr/lib/", "usr"); + check_dirname("usr/lib//", "usr"); + check_dirname(".git/", "."); + + check_dirname(REP16("/abc"), REP15("/abc")); +} + +/* get the base name of a path */ +void test_core_path__01_basename(void) +{ + check_basename(NULL, "."); + check_basename("", "."); + check_basename("a", "a"); + check_basename("/", "/"); + check_basename("/usr", "usr"); + check_basename("/usr/", "usr"); + check_basename("/usr/lib", "lib"); + check_basename("/usr/lib//", "lib"); + check_basename("usr/lib", "lib"); + + check_basename(REP16("/abc"), "abc"); + check_basename(REP1024("/abc"), "abc"); +} + +/* get the latest component in a path */ +void test_core_path__02_topdir(void) +{ + check_topdir(".git/", ".git/"); + check_topdir("/.git/", ".git/"); + check_topdir("usr/local/.git/", ".git/"); + check_topdir("./.git/", ".git/"); + check_topdir("/usr/.git/", ".git/"); + check_topdir("/", "/"); + check_topdir("a/", "a/"); + + cl_assert(git_path_topdir("/usr/.git") == NULL); + cl_assert(git_path_topdir(".") == NULL); + cl_assert(git_path_topdir("") == NULL); + cl_assert(git_path_topdir("a") == NULL); +} + +/* properly join path components */ +void test_core_path__05_joins(void) +{ + check_joinpath("", "", ""); + check_joinpath("", "a", "a"); + check_joinpath("", "/a", "/a"); + check_joinpath("a", "", "a/"); + check_joinpath("a", "/", "a/"); + check_joinpath("a", "b", "a/b"); + check_joinpath("/", "a", "/a"); + check_joinpath("/", "", "/"); + check_joinpath("/a", "/b", "/a/b"); + check_joinpath("/a", "/b/", "/a/b/"); + check_joinpath("/a/", "b/", "/a/b/"); + check_joinpath("/a/", "/b/", "/a/b/"); + + check_joinpath("/abcd", "/defg", "/abcd/defg"); + check_joinpath("/abcd", "/defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); + check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); + check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); + + check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); + check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); + check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); + + check_joinpath(REP1024("aaaa"), REP1024("bbbb"), + REP1024("aaaa") "/" REP1024("bbbb")); + check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), + REP1024("/aaaa") REP1024("/bbbb")); +} + +/* properly join path components for more than one path */ +void test_core_path__06_long_joins(void) +{ + check_joinpath_n("", "", "", "", ""); + check_joinpath_n("", "a", "", "", "a/"); + check_joinpath_n("a", "", "", "", "a/"); + check_joinpath_n("", "", "", "a", "a"); + check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); + check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); + check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); + check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); + check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); + + check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), + REP1024("a") "/" REP1024("b") "/" + REP1024("c") "/" REP1024("d")); + check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), + REP1024("/a") REP1024("/b") + REP1024("/c") REP1024("/d")); +} + + +static void +check_path_to_dir( + const char* path, + const char* expected) +{ + git_buf tgt = GIT_BUF_INIT; + + git_buf_sets(&tgt, path); + cl_git_pass(git_path_to_dir(&tgt)); + cl_assert_equal_s(expected, tgt.ptr); + + git_buf_free(&tgt); +} + +static void +check_string_to_dir( + const char* path, + size_t maxlen, + const char* expected) +{ + size_t len = strlen(path); + char *buf = git__malloc(len + 2); + cl_assert(buf); + + strncpy(buf, path, len + 2); + + git_path_string_to_dir(buf, maxlen); + + cl_assert_equal_s(expected, buf); + + git__free(buf); +} + +/* convert paths to dirs */ +void test_core_path__07_path_to_dir(void) +{ + check_path_to_dir("", ""); + check_path_to_dir(".", "./"); + check_path_to_dir("./", "./"); + check_path_to_dir("a/", "a/"); + check_path_to_dir("ab", "ab/"); + /* make sure we try just under and just over an expansion that will + * require a realloc + */ + check_path_to_dir("abcdef", "abcdef/"); + check_path_to_dir("abcdefg", "abcdefg/"); + check_path_to_dir("abcdefgh", "abcdefgh/"); + check_path_to_dir("abcdefghi", "abcdefghi/"); + check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); + check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); + + check_string_to_dir("", 1, ""); + check_string_to_dir(".", 1, "."); + check_string_to_dir(".", 2, "./"); + check_string_to_dir(".", 3, "./"); + check_string_to_dir("abcd", 3, "abcd"); + check_string_to_dir("abcd", 4, "abcd"); + check_string_to_dir("abcd", 5, "abcd/"); + check_string_to_dir("abcd", 6, "abcd/"); +} + +/* join path to itself */ +void test_core_path__08_self_join(void) +{ + git_buf path = GIT_BUF_INIT; + size_t asize = 0; + + asize = path.asize; + cl_git_pass(git_buf_sets(&path, "/foo")); + cl_assert_equal_s(path.ptr, "/foo"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_buf_joinpath(&path, path.ptr, "this is a new string")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_buf_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); + cl_assert(asize < path.asize); + + git_buf_free(&path); + cl_git_pass(git_buf_sets(&path, "/foo/bar")); + + cl_git_pass(git_buf_joinpath(&path, path.ptr + 4, "baz")); + cl_assert_equal_s(path.ptr, "/bar/baz"); + + asize = path.asize; + cl_git_pass(git_buf_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); + cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); + cl_assert(asize < path.asize); + + git_buf_free(&path); +} + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_equal_s(expected_result, git_buf_cstr(&buf)); + + git_buf_free(&buf); +} + +void test_core_path__09_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +} + +static void check_fromurl(const char *expected_result, const char *input, int should_fail) +{ + git_buf buf = GIT_BUF_INIT; + + assert(should_fail || expected_result); + + if (!should_fail) { + cl_git_pass(git_path_fromurl(&buf, input)); + cl_assert_equal_s(expected_result, git_buf_cstr(&buf)); + } else + cl_git_fail(git_path_fromurl(&buf, input)); + + git_buf_free(&buf); +} + +#ifdef _MSC_VER +#define ABS_PATH_MARKER "" +#else +#define ABS_PATH_MARKER "/" +#endif + +void test_core_path__10_fromurl(void) +{ + /* Failing cases */ + check_fromurl(NULL, "a", 1); + check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:///", 1); + check_fromurl(NULL, "file:////", 1); + check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); + + /* Passing cases */ + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); +} + +typedef struct { + int expect_idx; + char **expect; +} check_walkup_info; + +static int check_one_walkup_step(void *ref, git_buf *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_equal_s(info->expect[info->expect_idx], path->ptr); + info->expect_idx++; + return 0; +} + +void test_core_path__11_walkup(void) +{ + git_buf p = GIT_BUF_INIT; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + "this is a path", NULL, + "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", "/a/b", "/a/b/", NULL, NULL, NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_buf_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_equal_s(p.ptr, expect[i]); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_buf_free(&p); +} + +void test_core_path__12_offset_to_path_root(void) +{ + cl_assert(git_path_root("non/rooted/path") == -1); + cl_assert(git_path_root("/rooted/path") == 0); + +#ifdef GIT_WIN32 + /* Windows specific tests */ + cl_assert(git_path_root("C:non/rooted/path") == -1); + cl_assert(git_path_root("C:/rooted/path") == 2); + cl_assert(git_path_root("//computername/sharefolder/resource") == 14); + cl_assert(git_path_root("//computername/sharefolder") == 14); + cl_assert(git_path_root("//computername") == -1); +#endif +} + +#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" + +void test_core_path__13_cannot_prettify_a_non_existing_file(void) +{ + git_buf p = GIT_BUF_INIT; + + cl_must_pass(git_path_exists(NON_EXISTING_FILEPATH) == false); + cl_assert_equal_i(GIT_ENOTFOUND, git_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); + + git_buf_free(&p); +} diff --git a/tests-clar/core/pool.c b/tests-clar/core/pool.c new file mode 100644 index 000000000..5ed97366f --- /dev/null +++ b/tests-clar/core/pool.c @@ -0,0 +1,85 @@ +#include "clar_libgit2.h" +#include "pool.h" +#include "git2/oid.h" + +void test_core_pool__0(void) +{ + int i; + git_pool p; + void *ptr; + + cl_git_pass(git_pool_init(&p, 1, 4000)); + + for (i = 1; i < 10000; i *= 2) { + ptr = git_pool_malloc(&p, i); + cl_assert(ptr != NULL); + cl_assert(git_pool__ptr_in_pool(&p, ptr)); + cl_assert(!git_pool__ptr_in_pool(&p, &i)); + } + + /* 1+2+4+8+16+32+64+128+256+512+1024 -> original block */ + /* 2048 -> 1 block */ + /* 4096 -> 1 block */ + /* 8192 -> 1 block */ + + cl_assert(git_pool__open_pages(&p) + git_pool__full_pages(&p) == 4); + + git_pool_clear(&p); +} + +void test_core_pool__1(void) +{ + int i; + git_pool p; + + cl_git_pass(git_pool_init(&p, 1, 4000)); + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + + /* with fixed page size, allocation must end up with these values */ + cl_assert(git_pool__open_pages(&p) == 1); + cl_assert(git_pool__full_pages(&p) == 505); + + git_pool_clear(&p); + + cl_git_pass(git_pool_init(&p, 1, 4100)); + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + + /* with fixed page size, allocation must end up with these values */ + cl_assert(git_pool__open_pages(&p) == 1); + cl_assert(git_pool__full_pages(&p) == 492); + + git_pool_clear(&p); +} + +static char to_hex[] = "0123456789abcdef"; + +void test_core_pool__2(void) +{ + git_pool p; + char oid_hex[GIT_OID_HEXSZ]; + git_oid *oid; + int i, j; + + memset(oid_hex, '0', sizeof(oid_hex)); + + cl_git_pass(git_pool_init(&p, sizeof(git_oid), 100)); + + for (i = 1000; i < 10000; i++) { + oid = git_pool_malloc(&p, 1); + cl_assert(oid != NULL); + + for (j = 0; j < 8; j++) + oid_hex[j] = to_hex[(i >> (4 * j)) & 0x0f]; + cl_git_pass(git_oid_fromstr(oid, oid_hex)); + } + + /* with fixed page size, allocation must end up with these values */ + cl_assert(git_pool__open_pages(&p) == 0); + cl_assert(git_pool__full_pages(&p) == 90); + + git_pool_clear(&p); +} diff --git a/tests-clar/core/rmdir.c b/tests-clar/core/rmdir.c new file mode 100644 index 000000000..530f1f908 --- /dev/null +++ b/tests-clar/core/rmdir.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "fileops.h" + +static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; + +void test_core_rmdir__initialize(void) +{ + git_buf path = GIT_BUF_INIT; + + cl_must_pass(p_mkdir(empty_tmp_dir, 0777)); + + cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_two/three")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + git_buf_free(&path); +} + +/* make sure empty dir can be deleted recusively */ +void test_core_rmdir__delete_recursive(void) +{ + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); +} + +/* make sure non-empty dir cannot be deleted recusively */ +void test_core_rmdir__fail_to_delete_non_empty_dir(void) +{ + git_buf file = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_buf_cstr(&file), "dummy"); + + cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); + + cl_must_pass(p_unlink(file.ptr)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); + + git_buf_free(&file); +} + +void test_core_rmdir__can_skip__non_empty_dir(void) +{ + git_buf file = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_buf_cstr(&file), "dummy"); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS)); + cl_assert(git_path_exists(git_buf_cstr(&file)) == true); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_assert(git_path_exists(empty_tmp_dir) == false); + + git_buf_free(&file); +} diff --git a/tests-clar/core/string.c b/tests-clar/core/string.c new file mode 100644 index 000000000..bf6ec0a80 --- /dev/null +++ b/tests-clar/core/string.c @@ -0,0 +1,28 @@ +#include "clar_libgit2.h" + +/* compare prefixes */ +void test_core_string__0(void) +{ + cl_assert(git__prefixcmp("", "") == 0); + cl_assert(git__prefixcmp("a", "") == 0); + cl_assert(git__prefixcmp("", "a") < 0); + cl_assert(git__prefixcmp("a", "b") < 0); + cl_assert(git__prefixcmp("b", "a") > 0); + cl_assert(git__prefixcmp("ab", "a") == 0); + cl_assert(git__prefixcmp("ab", "ac") < 0); + cl_assert(git__prefixcmp("ab", "aa") > 0); +} + +/* compare suffixes */ +void test_core_string__1(void) +{ + cl_assert(git__suffixcmp("", "") == 0); + cl_assert(git__suffixcmp("a", "") == 0); + cl_assert(git__suffixcmp("", "a") < 0); + cl_assert(git__suffixcmp("a", "b") < 0); + cl_assert(git__suffixcmp("b", "a") > 0); + cl_assert(git__suffixcmp("ba", "a") == 0); + cl_assert(git__suffixcmp("zaa", "ac") < 0); + cl_assert(git__suffixcmp("zaz", "ac") > 0); +} + diff --git a/tests-clar/core/strmap.c b/tests-clar/core/strmap.c new file mode 100644 index 000000000..f34a4f89f --- /dev/null +++ b/tests-clar/core/strmap.c @@ -0,0 +1,102 @@ +#include "clar_libgit2.h" +#include "strmap.h" + +GIT__USE_STRMAP; + +void test_core_strmap__0(void) +{ + git_strmap *table = git_strmap_alloc(); + cl_assert(table != NULL); + cl_assert(git_strmap_num_entries(table) == 0); + git_strmap_free(table); +} + +static void insert_strings(git_strmap *table, int count) +{ + int i, j, over, err; + char *str; + + for (i = 0; i < count; ++i) { + str = malloc(10); + for (j = 0; j < 10; ++j) + str[j] = 'a' + (i % 26); + str[9] = '\0'; + + /* if > 26, then encode larger value in first letters */ + for (j = 0, over = i / 26; over > 0; j++, over = over / 26) + str[j] = 'A' + (over % 26); + + git_strmap_insert(table, str, str, err); + cl_assert(err >= 0); + } + + cl_assert((int)git_strmap_num_entries(table) == count); +} + +void test_core_strmap__1(void) +{ + int i; + char *str; + git_strmap *table = git_strmap_alloc(); + cl_assert(table != NULL); + + insert_strings(table, 20); + + cl_assert(git_strmap_exists(table, "aaaaaaaaa")); + cl_assert(git_strmap_exists(table, "ggggggggg")); + cl_assert(!git_strmap_exists(table, "aaaaaaaab")); + cl_assert(!git_strmap_exists(table, "abcdefghi")); + + i = 0; + git_strmap_foreach_value(table, str, { i++; free(str); }); + cl_assert(i == 20); + + git_strmap_free(table); +} + +void test_core_strmap__2(void) +{ + khiter_t pos; + int i; + char *str; + git_strmap *table = git_strmap_alloc(); + cl_assert(table != NULL); + + insert_strings(table, 20); + + cl_assert(git_strmap_exists(table, "aaaaaaaaa")); + cl_assert(git_strmap_exists(table, "ggggggggg")); + cl_assert(!git_strmap_exists(table, "aaaaaaaab")); + cl_assert(!git_strmap_exists(table, "abcdefghi")); + + cl_assert(git_strmap_exists(table, "bbbbbbbbb")); + pos = git_strmap_lookup_index(table, "bbbbbbbbb"); + cl_assert(git_strmap_valid_index(table, pos)); + cl_assert_equal_s(git_strmap_value_at(table, pos), "bbbbbbbbb"); + free(git_strmap_value_at(table, pos)); + git_strmap_delete_at(table, pos); + + cl_assert(!git_strmap_exists(table, "bbbbbbbbb")); + + i = 0; + git_strmap_foreach_value(table, str, { i++; free(str); }); + cl_assert(i == 19); + + git_strmap_free(table); +} + +void test_core_strmap__3(void) +{ + int i; + char *str; + git_strmap *table = git_strmap_alloc(); + cl_assert(table != NULL); + + insert_strings(table, 10000); + + i = 0; + git_strmap_foreach_value(table, str, { i++; free(str); }); + cl_assert(i == 10000); + + git_strmap_free(table); +} diff --git a/tests-clar/core/strtol.c b/tests-clar/core/strtol.c new file mode 100644 index 000000000..8765e042b --- /dev/null +++ b/tests-clar/core/strtol.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" + +void test_core_strtol__int32(void) +{ + int32_t i; + + cl_git_pass(git__strtol32(&i, "123", NULL, 10)); + cl_assert(i == 123); + cl_git_pass(git__strtol32(&i, " +123 ", NULL, 10)); + cl_assert(i == 123); + cl_git_pass(git__strtol32(&i, " +2147483647 ", NULL, 10)); + cl_assert(i == 2147483647); + cl_git_pass(git__strtol32(&i, " -2147483648 ", NULL, 10)); + cl_assert(i == -2147483648LL); + + cl_git_fail(git__strtol32(&i, " 2147483657 ", NULL, 10)); + cl_git_fail(git__strtol32(&i, " -2147483657 ", NULL, 10)); +} + +void test_core_strtol__int64(void) +{ + int64_t i; + + cl_git_pass(git__strtol64(&i, "123", NULL, 10)); + cl_assert(i == 123); + cl_git_pass(git__strtol64(&i, " +123 ", NULL, 10)); + cl_assert(i == 123); + cl_git_pass(git__strtol64(&i, " +2147483647 ", NULL, 10)); + cl_assert(i == 2147483647); + cl_git_pass(git__strtol64(&i, " -2147483648 ", NULL, 10)); + cl_assert(i == -2147483648LL); + cl_git_pass(git__strtol64(&i, " 2147483657 ", NULL, 10)); + cl_assert(i == 2147483657LL); + cl_git_pass(git__strtol64(&i, " -2147483657 ", NULL, 10)); + cl_assert(i == -2147483657LL); +} + diff --git a/tests-clar/core/vector.c b/tests-clar/core/vector.c new file mode 100644 index 000000000..ef3d6c36d --- /dev/null +++ b/tests-clar/core/vector.c @@ -0,0 +1,191 @@ +#include "clar_libgit2.h" +#include "vector.h" + +/* initial size of 1 would cause writing past array bounds */ +void test_core_vector__0(void) +{ + git_vector x; + int i; + git_vector_init(&x, 1, NULL); + for (i = 0; i < 10; ++i) { + git_vector_insert(&x, (void*) 0xabc); + } + git_vector_free(&x); +} + + +/* don't read past array bounds on remove() */ +void test_core_vector__1(void) +{ + git_vector x; + // make initial capacity exact for our insertions. + git_vector_init(&x, 3, NULL); + git_vector_insert(&x, (void*) 0xabc); + git_vector_insert(&x, (void*) 0xdef); + git_vector_insert(&x, (void*) 0x123); + + git_vector_remove(&x, 0); // used to read past array bounds. + git_vector_free(&x); +} + + +static int test_cmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +/* remove duplicates */ +void test_core_vector__2(void) +{ + git_vector x; + int *ptrs[2]; + + ptrs[0] = git__malloc(sizeof(int)); + ptrs[1] = git__malloc(sizeof(int)); + + *ptrs[0] = 2; + *ptrs[1] = 1; + + cl_git_pass(git_vector_init(&x, 5, test_cmp)); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_assert(x.length == 5); + + git_vector_uniq(&x); + cl_assert(x.length == 2); + + git_vector_free(&x); + + git__free(ptrs[0]); + git__free(ptrs[1]); +} + + +static int compare_them(const void *a, const void *b) +{ + return (int)((long)a - (long)b); +} + +/* insert_sorted */ +void test_core_vector__3(void) +{ + git_vector x; + long i; + git_vector_init(&x, 1, &compare_them); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 10); + for (i = 0; i < 10; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); + } + + git_vector_free(&x); +} + +/* insert_sorted with duplicates */ +void test_core_vector__4(void) +{ + git_vector x; + long i; + git_vector_init(&x, 1, &compare_them); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 20); + for (i = 0; i < 20; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); + } + + git_vector_free(&x); +} + +typedef struct { + int content; + int count; +} my_struct; + +static int _struct_count = 0; + +static int compare_structs(const void *a, const void *b) +{ + return ((const my_struct *)a)->content - + ((const my_struct *)b)->content; +} + +static int merge_structs(void **old_raw, void *new) +{ + my_struct *old = *(my_struct **)old_raw; + cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); + ((my_struct *)old)->count += 1; + git__free(new); + _struct_count--; + return GIT_EEXISTS; +} + +static my_struct *alloc_struct(int value) +{ + my_struct *st = git__malloc(sizeof(my_struct)); + st->content = value; + st->count = 0; + _struct_count++; + return st; +} + +/* insert_sorted with duplicates and special handling */ +void test_core_vector__5(void) +{ + git_vector x; + int i; + + git_vector_init(&x, 1, &compare_structs); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; ++i) { + cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); + git__free(git_vector_get(&x, i)); + _struct_count--; + } + + git_vector_free(&x); +} diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c new file mode 100644 index 000000000..6d7ad41d6 --- /dev/null +++ b/tests-clar/diff/blob.c @@ -0,0 +1,254 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; +static diff_expects expected; +static git_diff_options opts; +static git_blob *d, *alien; + +void test_diff_blob__initialize(void) +{ + git_oid oid; + + g_repo = cl_git_sandbox_init("attr"); + + memset(&opts, 0, sizeof(opts)); + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&expected, 0, sizeof(expected)); + + /* tests/resources/attr/root_test4.txt */ + cl_git_pass(git_oid_fromstrn(&oid, "fe773770c5a6", 12)); + cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 6)); + + /* alien.png */ + cl_git_pass(git_oid_fromstrn(&oid, "edf3dcee", 8)); + cl_git_pass(git_blob_lookup_prefix(&alien, g_repo, &oid, 4)); +} + +void test_diff_blob__cleanup(void) +{ + git_blob_free(d); + git_blob_free(alien); + + cl_git_sandbox_cleanup(); +} + +void test_diff_blob__can_compare_text_blobs(void) +{ + git_blob *a, *b, *c; + git_oid a_oid, b_oid, c_oid; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); + + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); + + /* tests/resources/attr/root_test3 */ + cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); + cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 8)); + + /* Doing the equivalent of a `git diff -U1` on these files */ + + cl_git_pass(git_diff_blobs( + a, b, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_mods == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 1); + cl_assert(expected.lines == 6); + cl_assert(expected.line_ctxt == 1); + cl_assert(expected.line_adds == 5); + cl_assert(expected.line_dels == 0); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + b, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_mods == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 1); + cl_assert(expected.lines == 15); + cl_assert(expected.line_ctxt == 3); + cl_assert(expected.line_adds == 9); + cl_assert(expected.line_dels == 3); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + a, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_mods == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 1); + cl_assert(expected.lines == 13); + cl_assert(expected.line_ctxt == 0); + cl_assert(expected.line_adds == 12); + cl_assert(expected.line_dels == 1); + + opts.context_lines = 1; + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + c, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_mods == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 2); + cl_assert(expected.lines == 14); + cl_assert(expected.line_ctxt == 4); + cl_assert(expected.line_adds == 6); + cl_assert(expected.line_dels == 4); + + git_blob_free(a); + git_blob_free(b); + git_blob_free(c); +} + +void test_diff_blob__can_compare_against_null_blobs(void) +{ + git_blob *e = NULL; + + cl_git_pass(git_diff_blobs( + d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_dels == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 1); + cl_assert(expected.hunk_old_lines == 14); + cl_assert(expected.lines == 14); + cl_assert(expected.line_dels == 14); + + opts.flags |= GIT_DIFF_REVERSE; + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.files == 1); + cl_assert(expected.file_adds == 1); + cl_assert(expected.at_least_one_of_them_is_binary == false); + + cl_assert(expected.hunks == 1); + cl_assert(expected.hunk_new_lines == 14); + cl_assert(expected.lines == 14); + cl_assert(expected.line_adds == 14); + + opts.flags ^= GIT_DIFF_REVERSE; + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + alien, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.at_least_one_of_them_is_binary == true); + + cl_assert(expected.files == 1); + cl_assert(expected.file_dels == 1); + cl_assert(expected.hunks == 0); + cl_assert(expected.lines == 0); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + NULL, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.at_least_one_of_them_is_binary == true); + + cl_assert(expected.files == 1); + cl_assert(expected.file_adds == 1); + cl_assert(expected.hunks == 0); + cl_assert(expected.lines == 0); +} + +static void assert_identical_blobs_comparison(diff_expects expected) +{ + cl_assert(expected.files == 1); + cl_assert(expected.file_unmodified == 1); + cl_assert(expected.hunks == 0); + cl_assert(expected.lines == 0); +} + +void test_diff_blob__can_compare_identical_blobs(void) +{ + cl_git_pass(git_diff_blobs( + d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.at_least_one_of_them_is_binary == false); + assert_identical_blobs_comparison(expected); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + NULL, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.at_least_one_of_them_is_binary == false); + assert_identical_blobs_comparison(expected); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + alien, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(expected.at_least_one_of_them_is_binary == true); + assert_identical_blobs_comparison(expected); +} + +static void assert_binary_blobs_comparison(diff_expects expected) +{ + cl_assert(expected.at_least_one_of_them_is_binary == true); + + cl_assert(expected.files == 1); + cl_assert(expected.file_mods == 1); + cl_assert(expected.hunks == 0); + cl_assert(expected.lines == 0); +} + +void test_diff_blob__can_compare_two_binary_blobs(void) +{ + git_blob *heart; + git_oid h_oid; + + /* heart.png */ + cl_git_pass(git_oid_fromstrn(&h_oid, "de863bff", 8)); + cl_git_pass(git_blob_lookup_prefix(&heart, g_repo, &h_oid, 4)); + + cl_git_pass(git_diff_blobs( + alien, heart, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + assert_binary_blobs_comparison(expected); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + heart, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + assert_binary_blobs_comparison(expected); + + git_blob_free(heart); +} + +void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) +{ + cl_git_pass(git_diff_blobs( + alien, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + assert_binary_blobs_comparison(expected); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + d, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + assert_binary_blobs_comparison(expected); +} diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c new file mode 100644 index 000000000..8587be9b1 --- /dev/null +++ b/tests-clar/diff/diff_helpers.c @@ -0,0 +1,99 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +git_tree *resolve_commit_oid_to_tree( + git_repository *repo, + const char *partial_oid) +{ + unsigned int len = (unsigned int)strlen(partial_oid); + git_oid oid; + git_object *obj = NULL; + git_tree *tree = NULL; + + if (git_oid_fromstrn(&oid, partial_oid, len) == 0) + git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY); + cl_assert(obj); + if (git_object_type(obj) == GIT_OBJ_TREE) + return (git_tree *)obj; + cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT); + cl_git_pass(git_commit_tree(&tree, (git_commit *)obj)); + git_object_free(obj); + return tree; +} + +int diff_file_fn( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + diff_expects *e = cb_data; + + GIT_UNUSED(progress); + + e-> at_least_one_of_them_is_binary = delta->binary; + + e->files++; + switch (delta->status) { + case GIT_DELTA_ADDED: e->file_adds++; break; + case GIT_DELTA_DELETED: e->file_dels++; break; + case GIT_DELTA_MODIFIED: e->file_mods++; break; + case GIT_DELTA_IGNORED: e->file_ignored++; break; + case GIT_DELTA_UNTRACKED: e->file_untracked++; break; + case GIT_DELTA_UNMODIFIED: e->file_unmodified++; break; + default: break; + } + return 0; +} + +int diff_hunk_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len) +{ + diff_expects *e = cb_data; + + GIT_UNUSED(delta); + GIT_UNUSED(header); + GIT_UNUSED(header_len); + + e->hunks++; + e->hunk_old_lines += range->old_lines; + e->hunk_new_lines += range->new_lines; + return 0; +} + +int diff_line_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, + const char *content, + size_t content_len) +{ + diff_expects *e = cb_data; + + GIT_UNUSED(delta); + GIT_UNUSED(range); + GIT_UNUSED(content); + GIT_UNUSED(content_len); + + e->lines++; + switch (line_origin) { + case GIT_DIFF_LINE_CONTEXT: + e->line_ctxt++; + break; + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_ADD_EOFNL: + e->line_adds++; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_DEL_EOFNL: + e->line_dels++; + break; + default: + break; + } + return 0; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h new file mode 100644 index 000000000..0aaa6c111 --- /dev/null +++ b/tests-clar/diff/diff_helpers.h @@ -0,0 +1,47 @@ +#include "fileops.h" +#include "git2/diff.h" + +extern git_tree *resolve_commit_oid_to_tree( + git_repository *repo, const char *partial_oid); + +typedef struct { + int files; + int file_adds; + int file_dels; + int file_mods; + int file_ignored; + int file_untracked; + int file_unmodified; + + int hunks; + int hunk_new_lines; + int hunk_old_lines; + + int lines; + int line_ctxt; + int line_adds; + int line_dels; + + bool at_least_one_of_them_is_binary; +} diff_expects; + +extern int diff_file_fn( + void *cb_data, + git_diff_delta *delta, + float progress); + +extern int diff_hunk_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len); + +extern int diff_line_fn( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, + const char *content, + size_t content_len); + diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c new file mode 100644 index 000000000..171815df5 --- /dev/null +++ b/tests-clar/diff/index.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_index__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_index__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* 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 = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 26a125ee1bf + * - git diff -U1 --cached 26a125ee1bf + * - mv .git .gitted + */ + cl_assert(exp.files == 8); + cl_assert(exp.file_adds == 3); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 8); + + cl_assert(exp.lines == 11); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 6); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 0017bd4ab1ec3 + * - git diff -U1 --cached 0017bd4ab1ec3 + * - mv .git .gitted + */ + cl_assert(exp.files == 12); + cl_assert(exp.file_adds == 7); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 12); + + cl_assert(exp.lines == 16); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 11); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c new file mode 100644 index 000000000..eee84810a --- /dev/null +++ b/tests-clar/diff/iterator.c @@ -0,0 +1,572 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" +#include "iterator.h" + +void test_diff_iterator__initialize(void) +{ + /* since we are doing tests with different sandboxes, defer setup + * to the actual tests. cleanup will still be done in the global + * cleanup function so that assertion failures don't result in a + * missed cleanup. + */ +} + +void test_diff_iterator__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +/* -- TREE ITERATOR TESTS -- */ + +static void tree_iterator_test( + const char *sandbox, + const char *treeish, + const char *start, + const char *end, + int expected_count, + const char **expected_values) +{ + git_tree *t; + git_iterator *i; + const git_index_entry *entry; + int count = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); + + cl_assert(t = resolve_commit_oid_to_tree(repo, treeish)); + cl_git_pass(git_iterator_for_tree_range(&i, repo, t, start, end)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count], entry->path); + + count++; + + cl_git_pass(git_iterator_advance(i, &entry)); + } + + git_iterator_free(i); + + cl_assert(expected_count == count); + + git_tree_free(t); +} + +/* results of: git ls-tree -r --name-only 605812a */ +const char *expected_tree_0[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_diff_iterator__tree_0(void) +{ + tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0); +} + +/* results of: git ls-tree -r --name-only 6bab5c79 */ +const char *expected_tree_1[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_diff_iterator__tree_1(void) +{ + tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1); +} + +/* results of: git ls-tree -r --name-only 26a125ee1 */ +const char *expected_tree_2[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL +}; + +void test_diff_iterator__tree_2(void) +{ + tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2); +} + +/* $ git ls-tree -r --name-only 0017bd4ab1e */ +const char *expected_tree_3[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file" +}; + +void test_diff_iterator__tree_3(void) +{ + tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3); +} + +/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */ +const char *expected_tree_4[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_diff_iterator__tree_4(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL, + 23, expected_tree_4); +} + +void test_diff_iterator__tree_4_ranged(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub", "sub", + 11, &expected_tree_4[12]); +} + +const char *expected_tree_ranged_0[] = { + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + NULL +}; + +void test_diff_iterator__tree_ranged_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "git", "root", + 7, expected_tree_ranged_0); +} + +const char *expected_tree_ranged_1[] = { + "sub/subdir_test2.txt", + NULL +}; + +void test_diff_iterator__tree_ranged_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub/subdir_test2.txt", "sub/subdir_test2.txt", + 1, expected_tree_ranged_1); +} + +void test_diff_iterator__tree_range_empty_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "empty", "empty", 0, NULL); +} + +void test_diff_iterator__tree_range_empty_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "z_empty_after", NULL, 0, NULL); +} + +void test_diff_iterator__tree_range_empty_2(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + NULL, ".aaa_empty_before", 0, NULL); +} + +/* -- INDEX ITERATOR TESTS -- */ + +static void index_iterator_test( + const char *sandbox, + const char *start, + const char *end, + int expected_count, + const char **expected_names, + const char **expected_oids) +{ + git_iterator *i; + const git_index_entry *entry; + int count = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); + + cl_git_pass(git_iterator_for_index_range(&i, repo, start, end)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count], entry->path); + + if (expected_oids != NULL) { + git_oid oid; + cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); + cl_assert_equal_i(git_oid_cmp(&oid, &entry->oid), 0); + } + + count++; + cl_git_pass(git_iterator_advance(i, &entry)); + } + + git_iterator_free(i); + + cl_assert_equal_i(expected_count, count); +} + +static const char *expected_index_0[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", +}; + +static const char *expected_index_oids_0[] = { + "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", + "3b74db7ab381105dc0d28f8295a77f6a82989292", + "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", + "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", + "d800886d9c86731ae5c4a62b0b77c437015e00d2", + "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", + "5819a185d77b03325aaf87cafc771db36f6ddca7", + "ff69f8639ce2e6010b3f33a74160aad98b48da2b", + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "fe773770c5a6cc7185580c9204b1ff18a33ff3fc", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "9e5bdc47d6a80f2be0ea3049ad74231b94609242", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "dccada462d3df8ac6de596fb8c896aba9344f941" +}; + +void test_diff_iterator__index_0(void) +{ + index_iterator_test( + "attr", NULL, NULL, 23, expected_index_0, expected_index_oids_0); +} + +static const char *expected_index_range[] = { + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", +}; + +static const char *expected_index_oids_range[] = { + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "fe773770c5a6cc7185580c9204b1ff18a33ff3fc", +}; + +void test_diff_iterator__index_range(void) +{ + index_iterator_test( + "attr", "root", "root", 4, expected_index_range, expected_index_oids_range); +} + +void test_diff_iterator__index_range_empty_0(void) +{ + index_iterator_test( + "attr", "empty", "empty", 0, NULL, NULL); +} + +void test_diff_iterator__index_range_empty_1(void) +{ + index_iterator_test( + "attr", "z_empty_after", NULL, 0, NULL, NULL); +} + +void test_diff_iterator__index_range_empty_2(void) +{ + index_iterator_test( + "attr", NULL, ".aaa_empty_before", 0, NULL, NULL); +} + +static const char *expected_index_1[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const char* expected_index_oids_1[] = { + "a0de7e0ac200c489c41c59dfa910154a70264e6e", + "5452d32f1dd538eb0405e8a83cc185f79e25e80f", + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", + "55d316c9ba708999f1918e9677d01dfcae69c6b9", + "a6be623522ce87a1d862128ac42672604f7b468b", + "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", + "529a16e8e762d4acb7b9636ff540a00831f9155a", + "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", + "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", + "e8ee89e15bbe9b20137715232387b3de5b28972e", + "53ace0d1cc1145a5f4fe4f78a186a60263190733", + "1888c805345ba265b0ee9449b8877b6064592058", + "a6191982709b746d5650e93c2acf34ef74e11504" +}; + +void test_diff_iterator__index_1(void) +{ + index_iterator_test( + "status", NULL, NULL, 13, expected_index_1, expected_index_oids_1); +} + + +/* -- WORKDIR ITERATOR TESTS -- */ + +static void workdir_iterator_test( + const char *sandbox, + const char *start, + const char *end, + int expected_count, + int expected_ignores, + const char **expected_names, + const char *an_ignored_name) +{ + git_iterator *i; + const git_index_entry *entry; + int count = 0, count_all = 0; + git_repository *repo = cl_git_sandbox_init(sandbox); + + cl_git_pass(git_iterator_for_workdir_range(&i, repo, start, end)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + int ignored = git_iterator_current_is_ignored(i); + + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into_directory(i, &entry)); + continue; + } + + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count_all], entry->path); + + if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) + cl_assert(ignored); + + if (!ignored) + count++; + count_all++; + + cl_git_pass(git_iterator_advance(i, &entry)); + } + + git_iterator_free(i); + + cl_assert(count == expected_count); + cl_assert(count_all == expected_count + expected_ignores); +} + +void test_diff_iterator__workdir_0(void) +{ + workdir_iterator_test("attr", NULL, NULL, 25, 2, NULL, "ign"); +} + +static const char *status_paths[] = { + "current_file", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_diff_iterator__workdir_1(void) +{ + workdir_iterator_test( + "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); +} + +static const char *status_paths_range_0[] = { + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + NULL +}; + +void test_diff_iterator__workdir_1_ranged_0(void) +{ + workdir_iterator_test( + "status", "staged", "staged", 5, 0, status_paths_range_0, NULL); +} + +static const char *status_paths_range_1[] = { + "modified_file", NULL +}; + +void test_diff_iterator__workdir_1_ranged_1(void) +{ + workdir_iterator_test( + "status", "modified_file", "modified_file", + 1, 0, status_paths_range_1, NULL); +} + +static const char *status_paths_range_3[] = { + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + NULL +}; + +void test_diff_iterator__workdir_1_ranged_3(void) +{ + workdir_iterator_test( + "status", "subdir", "subdir/modified_file", + 3, 0, status_paths_range_3, NULL); +} + +static const char *status_paths_range_4[] = { + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_diff_iterator__workdir_1_ranged_4(void) +{ + workdir_iterator_test( + "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); +} + +static const char *status_paths_range_5[] = { + "subdir/modified_file", + NULL +}; + +void test_diff_iterator__workdir_1_ranged_5(void) +{ + workdir_iterator_test( + "status", "subdir/modified_file", "subdir/modified_file", + 1, 0, status_paths_range_5, NULL); +} + +void test_diff_iterator__workdir_1_ranged_empty_0(void) +{ + workdir_iterator_test( + "status", "\xff_does_not_exist", NULL, + 0, 0, NULL, NULL); +} + +void test_diff_iterator__workdir_1_ranged_empty_1(void) +{ + workdir_iterator_test( + "status", "empty", "empty", + 0, 0, NULL, NULL); +} + +void test_diff_iterator__workdir_1_ranged_empty_2(void) +{ + workdir_iterator_test( + "status", NULL, "aaaa_empty_before", + 0, 0, NULL, NULL); +} diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c new file mode 100644 index 000000000..05e748667 --- /dev/null +++ b/tests-clar/diff/patch.c @@ -0,0 +1,99 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_patch__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_patch__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \ + "deleted file mode 100644\n" \ + "index e8ee89e..0000000\n" \ + "--- a/subdir.txt\n" \ + "+++ /dev/null\n" + +#define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n" + +static int check_removal_cb( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, + const char *formatted_output, + size_t output_len) +{ + GIT_UNUSED(cb_data); + GIT_UNUSED(output_len); + + switch (line_origin) { + case GIT_DIFF_LINE_FILE_HDR: + cl_assert_equal_s(EXPECTED_HEADER, formatted_output); + cl_assert(range == NULL); + goto check_delta; + + case GIT_DIFF_LINE_HUNK_HDR: + cl_assert_equal_s(EXPECTED_HUNK, formatted_output); + /* Fall through */ + + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_DELETION: + goto check_range; + + 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_delta: + cl_assert_equal_s("subdir.txt", delta->old_file.path); + cl_assert_equal_s("subdir.txt", delta->new_file.path); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + + return 0; +} + +void test_diff_patch__can_properly_display_the_removal_of_a_file(void) +{ + /* + * $ git diff 26a125e..735b6a2 + * diff --git a/subdir.txt b/subdir.txt + * deleted file mode 100644 + * index e8ee89e..0000000 + * --- a/subdir.txt + * +++ /dev/null + * @@ -1,2 +0,0 @@ + * -Is it a bird? + * -Is it a plane? + */ + + const char *one_sha = "26a125e"; + const char *another_sha = "735b6a2"; + git_tree *one, *another; + git_diff_list *diff; + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + another = resolve_commit_oid_to_tree(g_repo, another_sha); + + cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, one, another, &diff)); + + cl_git_pass(git_diff_print_patch(diff, NULL, check_removal_cb)); + + git_diff_list_free(diff); + + git_tree_free(another); + git_tree_free(one); +} diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c new file mode 100644 index 000000000..b932fa10e --- /dev/null +++ b/tests-clar/diff/tree.c @@ -0,0 +1,210 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_tree__initialize(void) +{ +} + +void test_diff_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_tree__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *a, *b, *c; + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 5); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 1); + cl_assert(exp.file_mods == 2); + + cl_assert(exp.hunks == 5); + + cl_assert(exp.lines == 7 + 24 + 1 + 6 + 6); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 24 + 1 + 5 + 5); + cl_assert(exp.line_dels == 7 + 1); + + git_diff_list_free(diff); + diff = NULL; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 2); + cl_assert(exp.file_adds == 0); + cl_assert(exp.file_dels == 0); + cl_assert(exp.file_mods == 2); + + cl_assert(exp.hunks == 2); + + cl_assert(exp.lines == 8 + 15); + cl_assert(exp.line_ctxt == 1); + cl_assert(exp.line_adds == 1); + cl_assert(exp.line_dels == 7 + 14); + + git_diff_list_free(diff); + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); +} + +void test_diff_tree__options(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "6bab5c79cd5140d0"; + const char *b_commit = "605812ab7fe421fdd"; + const char *c_commit = "f5b0af1fb4f5"; + const char *d_commit = "a97cc019851"; + git_tree *a, *b, *c, *d; + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects actual; + int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 }; + git_diff_options test_options[] = { + /* a vs b tests */ + { GIT_DIFF_NORMAL, 1, 1, NULL, NULL, {0} }, + { GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_REVERSE, 2, 1, NULL, NULL, {0} }, + { GIT_DIFF_FORCE_TEXT, 2, 1, NULL, NULL, {0} }, + /* c vs d tests */ + { GIT_DIFF_NORMAL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE_EOL, 3, 1, NULL, NULL, {0} }, + { GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1, 1, NULL, NULL, {0} }, + }; + /* to generate these values: + * - cd to tests/resources/attr, + * - mv .gitted .git + * - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd + * - mv .git .gitted + */ + diff_expects test_expects[] = { + /* a vs b tests */ + { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 }, + { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 }, + { 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 }, + { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 48, 3 }, + /* c vs d tests */ + { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 }, + { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 }, + { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 18, 11, 0, 7 }, + { 0 }, + }; + diff_expects *expected; + int i; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + cl_assert((d = resolve_commit_oid_to_tree(g_repo, d_commit)) != NULL); + + for (i = 0; test_expects[i].files > 0; i++) { + memset(&actual, 0, sizeof(actual)); /* clear accumulator */ + opts = test_options[i]; + + if (test_ab_or_cd[i] == 0) + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + else + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &actual, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + expected = &test_expects[i]; + cl_assert_equal_i(actual.files, expected->files); + cl_assert_equal_i(actual.file_adds, expected->file_adds); + cl_assert_equal_i(actual.file_dels, expected->file_dels); + cl_assert_equal_i(actual.file_mods, expected->file_mods); + cl_assert_equal_i(actual.hunks, expected->hunks); + cl_assert_equal_i(actual.lines, expected->lines); + cl_assert_equal_i(actual.line_ctxt, expected->line_ctxt); + 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); + diff = NULL; + } + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); + git_tree_free(d); +} + +void test_diff_tree__bare(void) +{ + const char *a_commit = "8496071c1b46c85"; + const char *b_commit = "be3563ae3f79"; + git_tree *a, *b; + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 3); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 0); + cl_assert(exp.file_mods == 1); + + cl_assert(exp.hunks == 3); + + cl_assert(exp.lines == 4); + cl_assert(exp.line_ctxt == 0); + cl_assert(exp.line_adds == 3); + cl_assert(exp.line_dels == 1); + + git_diff_list_free(diff); + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c new file mode 100644 index 000000000..42152f1ad --- /dev/null +++ b/tests-clar/diff/workdir.c @@ -0,0 +1,301 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_workdir__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_workdir__to_index(void) +{ + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status + * - git diff + * - mv .git .gitted + */ + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + + cl_assert_equal_i(8, exp.hunks); + + cl_assert_equal_i(14, exp.lines); + cl_assert_equal_i(5, exp.line_ctxt); + cl_assert_equal_i(4, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + + git_diff_list_free(diff); +} + +void test_diff_workdir__to_tree(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* 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 = {0}; + git_diff_list *diff = NULL; + git_diff_list *diff2 = NULL; + diff_expects exp; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + memset(&exp, 0, sizeof(exp)); + + /* You can't really generate the equivalent of git_diff_workdir_to_tree() + * using C git. It really wants to interpose the index into the diff. + * + * To validate the following results with command line git, I ran the + * following: + * - git ls-tree 26a125 + * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * The results are documented at the bottom of this file in the + * long comment entitled "PREPARATION OF TEST DATA". + */ + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 14); + cl_assert(exp.file_adds == 0); + cl_assert(exp.file_dels == 4); + cl_assert(exp.file_mods == 4); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 5); + + /* Since there is no git diff equivalent, let's just assume that the + * text diffs produced by git_diff_foreach are accurate here. We will + * do more apples-to-apples test comparison below. + */ + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* This is a compatible emulation of "git diff " which looks like + * a workdir to tree diff (even though it is not really). This is what + * you would get from "git diff --name-status 26a125ee1bf" + */ + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 15); + cl_assert(exp.file_adds == 2); + cl_assert(exp.file_dels == 5); + cl_assert(exp.file_mods == 4); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 3); + + cl_assert(exp.hunks == 11); + + cl_assert(exp.lines == 17); + cl_assert(exp.line_ctxt == 4); + cl_assert(exp.line_adds == 8); + cl_assert(exp.line_dels == 5); + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* Again, emulating "git diff " for testing purposes using + * "git diff --name-status 0017bd4ab1ec3" instead. + */ + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert(exp.files == 16); + cl_assert(exp.file_adds == 5); + cl_assert(exp.file_dels == 4); + cl_assert(exp.file_mods == 3); + cl_assert(exp.file_ignored == 1); + cl_assert(exp.file_untracked == 3); + + cl_assert(exp.hunks == 12); + + cl_assert(exp.lines == 19); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 12); + cl_assert(exp.line_dels == 4); + + git_diff_list_free(diff); + + git_tree_free(a); + git_tree_free(b); +} + +void test_diff_workdir__to_index_with_pathspec(void) +{ + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + + git_diff_list_free(diff); + + memset(&exp, 0, sizeof(exp)); + pathspec = "modified_file"; + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + + git_diff_list_free(diff); + + memset(&exp, 0, sizeof(exp)); + pathspec = "subdir"; + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(1, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(1, exp.file_untracked); + + git_diff_list_free(diff); + + memset(&exp, 0, sizeof(exp)); + pathspec = "*_deleted"; + + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(2, exp.file_dels); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + + git_diff_list_free(diff); +} + +/* PREPARATION OF TEST DATA + * + * Since there is no command line equivalent of git_diff_workdir_to_tree, + * it was a bit of a pain to confirm that I was getting the expected + * results in the first part of this tests. Here is what I ended up + * doing to set my expectation for the file counts and results: + * + * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows: + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted + * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file + * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes + * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted + * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file + * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file + * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file + * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file + * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file + * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes + * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file + * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file + * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * A - current_file (UNMODIFIED) -> not in results + * B D file_deleted + * M I ignored_file (IGNORED) + * C M modified_file + * N U new_file (UNTRACKED) + * D M staged_changes + * E D staged_changes_file_deleted + * F M staged_changes_modified_file + * G D staged_delete_file_deleted + * H - staged_delete_modified_file (UNMODIFIED) -> not in results + * O U staged_new_file + * P U staged_new_file_modified_file + * I - subdir/current_file (UNMODIFIED) -> not in results + * J D subdir/deleted_file + * K M subdir/modified_file + * Q U subdir/new_file + * L - subdir.txt (UNMODIFIED) -> not in results + * + * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR + */ diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c new file mode 100644 index 000000000..c657d4f71 --- /dev/null +++ b/tests-clar/index/read_tree.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "posix.h" + +/* Test that reading and writing a tree is a no-op */ +void test_index_read_tree__read_write_involution(void) +{ + git_repository *repo; + git_index *index; + git_oid tree_oid; + git_tree *tree; + git_oid expected; + + p_mkdir("read_tree", 0700); + + cl_git_pass(git_repository_init(&repo, "./read_tree", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + + p_mkdir("./read_tree/abc", 0700); + + /* Sort order: '-' < '/' < '_' */ + cl_git_mkfile("./read_tree/abc-d", NULL); + cl_git_mkfile("./read_tree/abc/d", NULL); + cl_git_mkfile("./read_tree/abc_d", NULL); + + cl_git_pass(git_index_add(index, "abc-d", 0)); + cl_git_pass(git_index_add(index, "abc_d", 0)); + cl_git_pass(git_index_add(index, "abc/d", 0)); + + /* write-tree */ + cl_git_pass(git_tree_create_fromindex(&expected, index)); + + /* read-tree */ + git_tree_lookup(&tree, repo, &expected); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); + cl_assert(git_oid_cmp(&expected, &tree_oid) == 0); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("read_tree"); +} diff --git a/tests-clar/index/rename.c b/tests-clar/index/rename.c new file mode 100644 index 000000000..eecd257fd --- /dev/null +++ b/tests-clar/index/rename.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" +#include "posix.h" + +void test_index_rename__single_file(void) +{ + git_repository *repo; + git_index *index; + int position; + git_oid expected; + git_index_entry *entry; + + p_mkdir("rename", 0700); + + cl_git_pass(git_repository_init(&repo, "./rename", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + + cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); + + /* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */ + cl_git_pass(git_index_add(index, "lame.name.txt", 0)); + cl_assert(git_index_entrycount(index) == 1); + + cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a")); + + position = git_index_find(index, "lame.name.txt"); + + entry = git_index_get(index, position); + cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); + + /* This removes the entry from the index, but not from the object database */ + cl_git_pass(git_index_remove(index, position)); + cl_assert(git_index_entrycount(index) == 0); + + p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt"); + + cl_git_pass(git_index_add(index, "fancy.name.txt", 0)); + cl_assert(git_index_entrycount(index) == 1); + + position = git_index_find(index, "fancy.name.txt"); + + entry = git_index_get(index, position); + cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("rename"); +} diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c new file mode 100644 index 000000000..3436f8d1e --- /dev/null +++ b/tests-clar/index/tests.c @@ -0,0 +1,246 @@ +#include "clar_libgit2.h" +#include "index.h" + +static const int index_entry_count = 109; +static const int 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") + + +// Suite data +struct test_entry { + int index; + char path[128]; + git_off_t file_size; + git_time_t mtime; +}; + +static struct test_entry test_entries[] = { + {4, "Makefile", 5064, 0x4C3F7F33}, + {62, "tests/Makefile", 2631, 0x4C3F7F33}, + {36, "src/index.c", 10014, 0x4C43368D}, + {6, "git.git-authors", 2709, 0x4C3F7F33}, + {48, "src/revobject.h", 1448, 0x4C3F7FE2} +}; + + +// Helpers +static void copy_file(const char *src, const char *dst) +{ + git_buf source_buf = GIT_BUF_INIT; + git_file dst_fd; + + cl_git_pass(git_futils_readbuffer(&source_buf, src)); + + dst_fd = git_futils_creat_withpath(dst, 0777, 0666); + if (dst_fd < 0) + goto cleanup; + + cl_git_pass(p_write(dst_fd, source_buf.ptr, source_buf.size)); + +cleanup: + git_buf_free(&source_buf); + p_close(dst_fd); +} + +static void files_are_equal(const char *a, const char *b) +{ + git_buf buf_a = GIT_BUF_INIT; + git_buf buf_b = GIT_BUF_INIT; + int pass; + + if (git_futils_readbuffer(&buf_a, a) < 0) + cl_assert(0); + + if (git_futils_readbuffer(&buf_b, b) < 0) { + git_buf_free(&buf_a); + cl_assert(0); + } + + pass = (buf_a.size == buf_b.size && !memcmp(buf_a.ptr, buf_b.ptr, buf_a.size)); + + git_buf_free(&buf_a); + git_buf_free(&buf_b); + + cl_assert(pass); +} + + +// Fixture setup and teardown +void test_index_tests__initialize(void) +{ +} + +void test_index_tests__cleanup(void) +{ +} + + +void test_index_tests__empty_index(void) +{ + git_index *index; + + cl_git_pass(git_index_open(&index, "in-memory-index")); + cl_assert(index->on_disk == 0); + + cl_assert(git_index_entrycount(index) == 0); + cl_assert(index->entries.sorted); + + git_index_free(index); +} + +void test_index_tests__default_test_index(void) +{ + git_index *index; + unsigned int i; + git_index_entry **entries; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_assert(index->on_disk); + + cl_assert(git_index_entrycount(index) == (unsigned int)index_entry_count); + cl_assert(index->entries.sorted); + + 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]; + + 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); +} + +void test_index_tests__gitgit_index(void) +{ + git_index *index; + + cl_git_pass(git_index_open(&index, TEST_INDEX2_PATH)); + cl_assert(index->on_disk); + + cl_assert(git_index_entrycount(index) == (unsigned int)index_entry_count_2); + cl_assert(index->entries.sorted); + cl_assert(index->tree != NULL); + + git_index_free(index); +} + +void test_index_tests__find_in_existing(void) +{ + git_index *index; + unsigned int i; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + + for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { + int idx = git_index_find(index, test_entries[i].path); + cl_assert(idx == test_entries[i].index); + } + + git_index_free(index); +} + +void test_index_tests__find_in_empty(void) +{ + git_index *index; + unsigned int i; + + cl_git_pass(git_index_open(&index, "fake-index")); + + for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { + int idx = git_index_find(index, test_entries[i].path); + cl_assert(idx == GIT_ENOTFOUND); + } + + git_index_free(index); +} + +void test_index_tests__write(void) +{ + git_index *index; + + copy_file(TEST_INDEXBIG_PATH, "index_rewrite"); + + cl_git_pass(git_index_open(&index, "index_rewrite")); + cl_assert(index->on_disk); + + cl_git_pass(git_index_write(index)); + files_are_equal(TEST_INDEXBIG_PATH, "index_rewrite"); + + git_index_free(index); + + p_unlink("index_rewrite"); +} + +void test_index_tests__sort0(void) +{ + // sort the entires in an index + /* + * TODO: This no longer applies: + * index sorting in Git uses some specific changes to the way + * directories are sorted. + * + * We need to specificially check for this by creating a new + * index, adding entries in random order and then + * checking for consistency + */ +} + +void test_index_tests__sort1(void) +{ + // sort the entires in an empty index + git_index *index; + + cl_git_pass(git_index_open(&index, "fake-index")); + + /* FIXME: this test is slightly dumb */ + cl_assert(index->entries.sorted); + + git_index_free(index); +} + +void test_index_tests__add(void) +{ + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + git_repository *repo; + git_index_entry *entry; + git_oid id1; + + /* Intialize a new repository */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + /* 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_write(&file, "hey there\n", 10)); + cl_git_pass(git_filebuf_commit(&file, 0666)); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ echo "hey there" | git hash-object --stdin + */ + cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + + /* Add the new file to the index */ + cl_git_pass(git_index_add(index, "test.txt", 0)); + + /* Wow... it worked! */ + cl_assert(git_index_entrycount(index) == 1); + entry = git_index_get(index, 0); + + /* And the built-in hashing mechanism worked as expected */ + cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + + git_index_free(index); + git_repository_free(repo); +} + diff --git a/tests-clar/network/createremotethenload.c b/tests-clar/network/createremotethenload.c new file mode 100644 index 000000000..45931d376 --- /dev/null +++ b/tests-clar/network/createremotethenload.c @@ -0,0 +1,33 @@ +#include "clar_libgit2.h" + +static git_remote *_remote; +static git_repository *_repo; +static git_config *_config; +static char url[] = "http://github.com/libgit2/libgit2.git"; + +void test_network_createremotethenload__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); + + cl_git_pass(git_repository_config(&_config, _repo)); + cl_git_pass(git_config_set_string(_config, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")); + cl_git_pass(git_config_set_string(_config, "remote.origin.url", url)); + git_config_free(_config); + + cl_git_pass(git_remote_load(&_remote, _repo, "origin")); +} + +void test_network_createremotethenload__cleanup(void) +{ + git_remote_free(_remote); + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_network_createremotethenload__parsing(void) +{ + cl_assert_equal_s(git_remote_name(_remote), "origin"); + cl_assert_equal_s(git_remote_url(_remote), url); +} diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c new file mode 100644 index 000000000..8cee7ce08 --- /dev/null +++ b/tests-clar/network/remotelocal.c @@ -0,0 +1,137 @@ +#include "clar_libgit2.h" +#include "transport.h" +#include "buffer.h" +#include "path.h" +#include "posix.h" + +static git_repository *repo; +static git_buf file_path_buf = GIT_BUF_INIT; +static git_remote *remote; + +static void build_local_file_url(git_buf *out, const char *fixture) +{ + const char *in_buf; + + git_buf path_buf = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL)); + cl_git_pass(git_buf_puts(out, "file://")); + +#ifdef _MSC_VER + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_buf_putc(out, '/')); +#endif + + in_buf = git_buf_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_buf_puts(out, "%20")); + else + cl_git_pass(git_buf_putc(out, *in_buf)); + + in_buf++; + } + + git_buf_free(&path_buf); +} + +void test_network_remotelocal__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "remotelocal/", 0)); + cl_assert(repo != NULL); +} + +void test_network_remotelocal__cleanup(void) +{ + git_remote_free(remote); + git_buf_free(&file_path_buf); + git_repository_free(repo); + 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) +{ + build_local_file_url(&file_path_buf, local_repository); + + cl_git_pass(git_remote_new(&remote, repo, NULL, git_buf_cstr(&file_path_buf), NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH)); + +} + +void test_network_remotelocal__connected(void) +{ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_assert(git_remote_connected(remote)); + + git_remote_disconnect(remote); + cl_assert(!git_remote_connected(remote)); +} + +void test_network_remotelocal__retrieve_advertised_references(void) +{ + int how_many_refs = 0; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); + + cl_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */ +} + +void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void) +{ + int how_many_refs = 0; + + 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_assert(how_many_refs == 14); /* 1 HEAD + 6 heads + 1 lightweight tag + 3 annotated tags + 3 peeled target */ + + git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ + remote = NULL; + + cl_fixture_cleanup("spaced testrepo.git"); +} + +void test_network_remotelocal__nested_tags_are_completely_peeled(void) +{ + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_ls(remote, &ensure_peeled__cb, NULL)); +} diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c new file mode 100644 index 000000000..eb7947dfb --- /dev/null +++ b/tests-clar/network/remotes.c @@ -0,0 +1,183 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refspec.h" +#include "transport.h" + +static git_remote *_remote; +static git_repository *_repo; +static const git_refspec *_refspec; + +void test_network_remotes__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); + cl_git_pass(git_remote_load(&_remote, _repo, "test")); + + _refspec = git_remote_fetchspec(_remote); + cl_assert(_refspec != NULL); +} + +void test_network_remotes__cleanup(void) +{ + git_remote_free(_remote); + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_network_remotes__parsing(void) +{ + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); +} + +void test_network_remotes__parsing_ssh_remote(void) +{ + cl_assert( git_remote_valid_url("git@github.com:libgit2/libgit2.git") ); +} + +void test_network_remotes__parsing_local_path_fails_if_path_not_found(void) +{ + cl_assert( !git_remote_valid_url("/home/git/repos/libgit2.git") ); +} + +void test_network_remotes__supported_transport_methods_are_supported(void) +{ + cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") ); +} + +void test_network_remotes__unsupported_transport_methods_are_unsupported(void) +{ + cl_assert( !git_remote_supported_url("git@github.com:libgit2/libgit2.git") ); +} + +void test_network_remotes__refspec_parsing(void) +{ + cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/test/*"); +} + +void test_network_remotes__set_fetchspec(void) +{ + cl_git_pass(git_remote_set_fetchspec(_remote, "refs/*:refs/*")); + _refspec = git_remote_fetchspec(_remote); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); +} + +void test_network_remotes__set_pushspec(void) +{ + cl_git_pass(git_remote_set_pushspec(_remote, "refs/*:refs/*")); + _refspec = git_remote_pushspec(_remote); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); +} + +void test_network_remotes__save(void) +{ + git_remote_free(_remote); + + /* Set up the remote and save it to config */ + cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL)); + cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); + cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); + cl_git_pass(git_remote_save(_remote)); + git_remote_free(_remote); + _remote = NULL; + + /* Load it from config and make sure everything matches */ + cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); + + _refspec = git_remote_fetchspec(_remote); + cl_assert(_refspec != NULL); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/upstream/*"); + cl_assert(git_refspec_force(_refspec) == 0); + + _refspec = git_remote_pushspec(_remote); + cl_assert(_refspec != NULL); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); +} + +void test_network_remotes__fnmatch(void) +{ + cl_assert(git_refspec_src_matches(_refspec, "refs/heads/master")); + cl_assert(git_refspec_src_matches(_refspec, "refs/heads/multi/level/branch")); +} + +void test_network_remotes__transform(void) +{ + char ref[1024]; + + memset(ref, 0x0, sizeof(ref)); + cl_git_pass(git_refspec_transform(ref, sizeof(ref), _refspec, "refs/heads/master")); + cl_assert_equal_s(ref, "refs/remotes/test/master"); +} + +void test_network_remotes__transform_r(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_refspec_transform_r(&buf, _refspec, "refs/heads/master")); + cl_assert_equal_s(git_buf_cstr(&buf), "refs/remotes/test/master"); + git_buf_free(&buf); +} + +void test_network_remotes__missing_refspecs(void) +{ + git_config *cfg; + + git_remote_free(_remote); + + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + cl_git_pass(git_remote_load(&_remote, _repo, "specless")); + + git_config_free(cfg); +} + +void test_network_remotes__list(void) +{ + git_strarray list; + git_config *cfg; + + cl_git_pass(git_remote_list(&list, _repo)); + cl_assert(list.count == 1); + git_strarray_free(&list); + + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + cl_git_pass(git_remote_list(&list, _repo)); + cl_assert(list.count == 2); + git_strarray_free(&list); + + git_config_free(cfg); +} + +void test_network_remotes__loading_a_missing_remote_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_remote_load(&_remote, _repo, "just-left-few-minutes-ago")); +} + +/* + * $ git remote add addtest http://github.com/libgit2/libgit2 + * + * $ cat .git/config + * [...] + * [remote "addtest"] + * url = http://github.com/libgit2/libgit2 + * fetch = +refs/heads/\*:refs/remotes/addtest/\* + */ +void test_network_remotes__add(void) +{ + git_remote_free(_remote); + cl_git_pass(git_remote_add(&_remote, _repo, "addtest", "http://github.com/libgit2/libgit2")); + git_remote_free(_remote); + + cl_git_pass(git_remote_load(&_remote, _repo, "addtest")); + _refspec = git_remote_fetchspec(_remote); + cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*")); + cl_assert(git_refspec_force(_refspec) == 1); + cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*")); +} diff --git a/tests-clar/notes/notes.c b/tests-clar/notes/notes.c new file mode 100644 index 000000000..5185f25ea --- /dev/null +++ b/tests-clar/notes/notes.c @@ -0,0 +1,133 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_signature *_sig; + +void test_notes_notes__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); +} + +void test_notes_notes__cleanup(void) +{ + git_signature_free(_sig); + cl_git_sandbox_cleanup(); +} + +static void create_note(git_oid *note_oid, const char *canonical_namespace, const char *target_sha, const char *message) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, target_sha)); + cl_git_pass(git_note_create(note_oid, _repo, _sig, _sig, canonical_namespace, &oid, message)); +} + +void test_notes_notes__1(void) +{ + git_oid oid, note_oid; + static git_note *note; + static git_blob *blob; + + cl_git_pass(git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_note_create(¬e_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &oid, "hello world\n")); + cl_git_pass(git_note_create(¬e_oid, _repo, _sig, _sig, NULL, &oid, "hello world\n")); + + cl_git_pass(git_note_read(¬e, _repo, NULL, &oid)); + + cl_assert_equal_s(git_note_message(note), "hello world\n"); + cl_assert(!git_oid_cmp(git_note_oid(note), ¬e_oid)); + + cl_git_pass(git_blob_lookup(&blob, _repo, ¬e_oid)); + cl_assert_equal_s(git_note_message(note), git_blob_rawcontent(blob)); + + cl_git_fail(git_note_create(¬e_oid, _repo, _sig, _sig, NULL, &oid, "hello world\n")); + cl_git_fail(git_note_create(¬e_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &oid, "hello world\n")); + + cl_git_pass(git_note_remove(_repo, NULL, _sig, _sig, &oid)); + cl_git_pass(git_note_remove(_repo, "refs/notes/some/namespace", _sig, _sig, &oid)); + + cl_git_fail(git_note_remove(_repo, NULL, _sig, _sig, ¬e_oid)); + cl_git_fail(git_note_remove(_repo, "refs/notes/some/namespace", _sig, _sig, &oid)); + + git_note_free(note); + git_blob_free(blob); +} + +static struct { + const char *note_sha; + const char *annotated_object_sha; +} list_expectations[] = { + { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }, + { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "9fd738e8f7967c078dceed8190330fc8648ee56a" }, + { "257b43746b6b46caa4aa788376c647cce0a33e2b", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750" }, + { "1ec1c8e03f461f4f5d3f3702172483662e7223f3", "c47800c7266a2be04c571c04d5a6614691ea99bd" }, + { NULL, NULL } +}; + +#define EXPECTATIONS_COUNT (sizeof(list_expectations)/sizeof(list_expectations[0])) - 1 + +static int note_list_cb(git_note_data *note_data, void *payload) +{ + git_oid expected_note_oid, expected_target_oid; + + unsigned int *count = (unsigned int *)payload; + + cl_assert(*count < EXPECTATIONS_COUNT); + + cl_git_pass(git_oid_fromstr(&expected_note_oid, list_expectations[*count].note_sha)); + cl_assert(git_oid_cmp(&expected_note_oid, ¬e_data->blob_oid) == 0); + + cl_git_pass(git_oid_fromstr(&expected_target_oid, list_expectations[*count].annotated_object_sha)); + cl_assert(git_oid_cmp(&expected_target_oid, ¬e_data->annotated_object_oid) == 0); + + (*count)++; + + return 0; +} + +/* + * $ git notes --ref i-can-see-dead-notes add -m "I decorate a65f" a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * $ git notes --ref i-can-see-dead-notes add -m "I decorate c478" c47800c7266a2be04c571c04d5a6614691ea99bd + * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 9fd738e8f7967c078dceed8190330fc8648ee56a + * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * + * $ git notes --ref i-can-see-dead-notes list + * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a + * 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd + * + * $ git ls-tree refs/notes/i-can-see-dead-notes + * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a + * 100644 blob 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * 100644 blob 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd +*/ +void test_notes_notes__can_retrieve_a_list_of_notes_for_a_given_namespace(void) +{ + git_oid note_oid1, note_oid2, note_oid3, note_oid4; + unsigned int retrieved_notes = 0; + + create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); + create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); + create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); + create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); + + cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes)); + + cl_assert_equal_i(4, retrieved_notes); +} + +void test_notes_notes__retrieving_a_list_of_notes_for_an_unknown_namespace_returns_ENOTFOUND(void) +{ + int error; + unsigned int retrieved_notes = 0; + + error = git_note_foreach(_repo, "refs/notes/i-am-not", note_list_cb, &retrieved_notes); + cl_git_fail(error); + cl_assert_equal_i(GIT_ENOTFOUND, error); + + cl_assert_equal_i(0, retrieved_notes); +} diff --git a/tests-clar/notes/notesref.c b/tests-clar/notes/notesref.c new file mode 100644 index 000000000..79ad0afee --- /dev/null +++ b/tests-clar/notes/notesref.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" + +#include "notes.h" + +static git_repository *_repo; +static git_note *_note; +static git_signature *_sig; +static git_config *_cfg; + +void test_notes_notesref__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_notes_notesref__cleanup(void) +{ + git_note_free(_note); + git_signature_free(_sig); + git_config_free(_cfg); + + git_repository_free(_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_notes_notesref__config_corenotesref(void) +{ + git_oid oid, note_oid; + const char *default_ref; + + cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); + cl_git_pass(git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_repository_config(&_cfg, _repo)); + + cl_git_pass(git_config_set_string(_cfg, "core.notesRef", "refs/notes/mydefaultnotesref")); + + cl_git_pass(git_note_create(¬e_oid, _repo, _sig, _sig, NULL, &oid, "test123test\n")); + + cl_git_pass(git_note_read(&_note, _repo, NULL, &oid)); + cl_assert(!strcmp(git_note_message(_note), "test123test\n")); + cl_assert(!git_oid_cmp(git_note_oid(_note), ¬e_oid)); + + git_note_free(_note); + + cl_git_pass(git_note_read(&_note, _repo, "refs/notes/mydefaultnotesref", &oid)); + cl_assert(!strcmp(git_note_message(_note), "test123test\n")); + cl_assert(!git_oid_cmp(git_note_oid(_note), ¬e_oid)); + + cl_git_pass(git_note_default_ref(&default_ref, _repo)); + cl_assert(!strcmp(default_ref, "refs/notes/mydefaultnotesref")); + + cl_git_pass(git_config_delete(_cfg, "core.notesRef")); + + cl_git_pass(git_note_default_ref(&default_ref, _repo)); + cl_assert(!strcmp(default_ref, GIT_NOTES_DEFAULT_REF)); +} diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c new file mode 100644 index 000000000..0b87b2b46 --- /dev/null +++ b/tests-clar/object/blob/filter.c @@ -0,0 +1,125 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" + +static git_repository *g_repo = NULL; +#define NUM_TEST_OBJECTS 6 +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" +}; +static int g_len[NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17 }; +static git_text_stats g_stats[NUM_TEST_OBJECTS] = { + { 0, 0, 0, 0, 0, 0 }, + { 0, 0, 2, 0, 6, 0 }, + { 0, 2, 0, 0, 6, 0 }, + { 0, 2, 2, 2, 6, 0 }, + { 0, 4, 4, 1, 31, 0 }, + { 1, 1, 2, 1, 9, 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 } +}; + +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] = (int)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((size_t)g_len[i] == git_blob_rawsize(blob)); + cl_assert(memcmp(git_blob_rawcontent(blob), g_raw[i], 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_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_text_gather_stats(&stats, &buf); + 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/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c new file mode 100644 index 000000000..228e969b6 --- /dev/null +++ b/tests-clar/object/blob/fromchunks.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static char textual_content[] = "libgit2\n\r\n\0"; + +void test_object_blob_fromchunks__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_blob_fromchunks__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int text_chunked_source_cb(char *content, size_t max_length, void *payload) +{ + int *count; + + GIT_UNUSED(max_length); + + count = (int *)payload; + (*count)--; + + if (*count == 0) + return 0; + + strcpy(content, textual_content); + return strlen(textual_content); +} + +void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void) +{ + git_oid expected_oid, oid; + git_object *blob; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_fail(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + + 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)); + git_object_free(blob); +} + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void write_attributes(git_repository *repo) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); + cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); + + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); + cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); + + git_buf_free(&buf); +} + +static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) +{ + git_oid expected_oid, oid; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + + cl_git_pass(git_blob_create_fromchunks(&oid, repo, fake_name, text_chunked_source_cb, &howmany)); + cl_assert(git_oid_cmp(&expected_oid, &oid) == 0); +} + +void test_object_blob_fromchunks__creating_a_blob_from_chunks_honors_the_attributes_directives(void) +{ + write_attributes(repo); + + assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); +} diff --git a/tests-clar/object/blob/write.c b/tests-clar/object/blob/write.c new file mode 100644 index 000000000..722c7b956 --- /dev/null +++ b/tests-clar/object/blob/write.c @@ -0,0 +1,69 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; + +#define WORKDIR "empty_standard_repo" +#define BARE_REPO "testrepo.git" +#define ELSEWHERE "elsewhere" + +typedef int (*blob_creator_fn)( + git_oid *, + git_repository *, + const char *); + +void test_object_blob_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_blob_creation(const char *path_to_file, const char *blob_from_path, blob_creator_fn creator) +{ + git_oid oid; + cl_git_mkfile(path_to_file, "1..2...3... Can you hear me?\n"); + + cl_must_pass(creator(&oid, repo, blob_from_path)); + cl_assert(git_oid_streq(&oid, "da5e4f20c91c81b44a7e298f3d3fb3fe2f178e32") == 0); +} + +void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_file_located_in_the_working_directory(void) +{ + repo = cl_git_sandbox_init(WORKDIR); + + assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_fromfile); +} + +void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolute_filepath_pointing_outside_of_the_working_directory(void) +{ + git_buf full_path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init(WORKDIR); + + cl_must_pass(p_mkdir(ELSEWHERE, 0777)); + cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL)); + cl_must_pass(git_buf_puts(&full_path, "test.txt")); + + assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); + + git_buf_free(&full_path); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS)); +} + +void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void) +{ + git_buf full_path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init(BARE_REPO); + + cl_must_pass(p_mkdir(ELSEWHERE, 0777)); + cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL)); + cl_must_pass(git_buf_puts(&full_path, "test.txt")); + + assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); + + git_buf_free(&full_path); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS)); +} diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests-clar/object/commit/commitstagedfile.c new file mode 100644 index 000000000..a852458f4 --- /dev/null +++ b/tests-clar/object/commit/commitstagedfile.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static git_repository *repo; + +void test_object_commit_commitstagedfile__initialize(void) +{ + cl_fixture("treebuilder"); + cl_git_pass(git_repository_init(&repo, "treebuilder/", 0)); + cl_assert(repo != NULL); +} + +void test_object_commit_commitstagedfile__cleanup(void) +{ + git_repository_free(repo); + cl_fixture_cleanup("treebuilder"); +} + +void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) +{ + git_index *index; + git_index_entry *entry; + git_oid expected_blob_oid, tree_oid, expected_tree_oid, commit_oid, expected_commit_oid; + git_signature *signature; + git_tree *tree; + + /* + * The test below replicates the following git scenario + * + * $ echo "test" > test.txt + * $ git hash-object test.txt + * 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 + * + * $ git add . + * $ git commit -m "Initial commit" + * + * $ git log + * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d + * Author: nulltoken + * Date: Wed Dec 14 08:29:03 2011 +0100 + * + * Initial commit + * + * $ git show 1fe3 --format=raw + * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d + * tree 2b297e643c551e76cfa1f93810c50811382f9117 + * author nulltoken 1323847743 +0100 + * committer nulltoken 1323847743 +0100 + * + * Initial commit + * + * diff --git a/test.txt b/test.txt + * new file mode 100644 + * index 0000000..9daeafb + * --- /dev/null + * +++ b/test.txt + * @@ -0,0 +1 @@ + * +test + * + * $ git ls-tree 2b297 + * 100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt + */ + + cl_git_pass(git_oid_fromstr(&expected_commit_oid, "1fe3126578fc4eca68c193e4a3a0a14a0704624d")); + cl_git_pass(git_oid_fromstr(&expected_tree_oid, "2b297e643c551e76cfa1f93810c50811382f9117")); + cl_git_pass(git_oid_fromstr(&expected_blob_oid, "9daeafb9864cf43055ae93beb0afd6c7d144bfa4")); + + /* + * Add a new file to the index + */ + cl_git_mkfile("treebuilder/test.txt", "test\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, "test.txt", 0)); + + entry = git_index_get(index, 0); + + cl_assert(git_oid_cmp(&expected_blob_oid, &entry->oid) == 0); + + /* + * Information about index entry should match test file + */ + { + struct stat st; + cl_must_pass(p_lstat("treebuilder/test.txt", &st)); + cl_assert(entry->file_size == st.st_size); +#ifndef _WIN32 + /* + * Windows doesn't populate these fields, and the signage is + * wrong in the Windows version of the struct, so lets avoid + * the "comparing signed and unsigned" compilation warning in + * that case. + */ + cl_assert(entry->uid == st.st_uid); + cl_assert(entry->gid == st.st_gid); +#endif + } + + /* + * Build the tree from the index + */ + cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); + + cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0); + + /* + * Commit the staged file + */ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); + 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)); + + cl_assert(git_oid_cmp(&expected_commit_oid, &commit_oid) == 0); + + git_signature_free(signature); + git_tree_free(tree); + git_index_free(index); +} diff --git a/tests-clar/object/lookup.c b/tests-clar/object/lookup.c new file mode 100644 index 000000000..7cbcc6140 --- /dev/null +++ b/tests-clar/object/lookup.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static git_repository *g_repo; + +void test_object_lookup__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_object_lookup__cleanup(void) +{ + git_repository_free(g_repo); +} + +void test_object_lookup__lookup_wrong_type_returns_enotfound(void) +{ + const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG)); +} + +void test_object_lookup__lookup_nonexisting_returns_enotfound(void) +{ + const char *unknown = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, unknown)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_ANY)); +} + +void test_object_lookup__lookup_wrong_type_by_abbreviated_id_returns_enotfound(void) +{ + const char *commit = "e90810b"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstrn(&oid, commit, strlen(commit))); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup_prefix(&object, g_repo, &oid, strlen(commit), GIT_OBJ_TAG)); +} + +void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void) +{ + const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + + cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT)); + git_object_free(object); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG)); +} diff --git a/tests-clar/object/message.c b/tests-clar/object/message.c new file mode 100644 index 000000000..cbdc80a64 --- /dev/null +++ b/tests-clar/object/message.c @@ -0,0 +1,171 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "message.h" + +static void assert_message_prettifying(char *expected_output, char *input, int strip_comments) +{ + git_buf prettified_message = GIT_BUF_INIT; + + git_message_prettify(&prettified_message, input, strip_comments); + cl_assert_equal_s(expected_output, git_buf_cstr(&prettified_message)); + + git_buf_free(&prettified_message); +} + +#define t40 "A quick brown fox jumps over the lazy do" +#define s40 " " +#define sss s40 s40 s40 s40 s40 s40 s40 s40 s40 s40 // # 400 +#define ttt t40 t40 t40 t40 t40 t40 t40 t40 t40 t40 // # 400 + +/* Ported from git.git */ +/* see https://github.com/git/git/blob/master/t/t0030-stripspace.sh */ +void test_object_message__long_lines_without_spaces_should_be_unchanged(void) +{ + assert_message_prettifying(ttt "\n", ttt, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); +} + +void test_object_message__lines_with_spaces_at_the_beginning_should_be_unchanged(void) +{ + assert_message_prettifying(sss ttt "\n", sss ttt, 0); + assert_message_prettifying(sss sss ttt "\n", sss sss ttt, 0); + assert_message_prettifying(sss sss sss ttt "\n", sss sss sss ttt, 0); +} + +void test_object_message__lines_with_intermediate_spaces_should_be_unchanged(void) +{ + assert_message_prettifying(ttt sss ttt "\n", ttt sss ttt, 0); + assert_message_prettifying(ttt sss sss ttt "\n", ttt sss sss ttt, 0); +} + +void test_object_message__consecutive_blank_lines_should_be_unified(void) +{ + assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\n\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\n\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\n\n\n\n" ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt ttt "\n", 0); +} + +void test_object_message__only_consecutive_blank_lines_should_be_completely_removed(void) +{ + assert_message_prettifying("", "\n", 0); + assert_message_prettifying("", "\n\n\n", 0); + assert_message_prettifying("", sss "\n" sss "\n" sss "\n", 0); + assert_message_prettifying("", sss sss "\n" sss "\n\n", 0); +} + +void test_object_message__consecutive_blank_lines_at_the_beginning_should_be_removed(void) +{ + assert_message_prettifying(ttt "\n", "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n", "\n\n\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss "\n" sss "\n" sss "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n" sss "\n" sss sss "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss sss "\n" sss "\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss sss sss "\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n" sss sss sss "\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n\n" sss sss sss "\n" ttt "\n", 0); +} + +void test_object_message__consecutive_blank_lines_at_the_end_should_be_removed(void) +{ + assert_message_prettifying(ttt "\n", ttt "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss "\n" sss "\n" sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n" sss "\n" sss sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss sss "\n" sss "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss sss sss "\n\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n" sss sss sss "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n\n" sss sss sss "\n\n", 0); +} + +void test_object_message__text_without_newline_at_end_should_end_with_newline(void) +{ + assert_message_prettifying(ttt "\n", ttt, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); +} + +void test_object_message__text_plus_spaces_without_newline_should_not_show_spaces_and_end_with_newline(void) +{ + assert_message_prettifying(ttt "\n", ttt sss, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss, 0); + assert_message_prettifying(ttt "\n", ttt sss sss, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss, 0); + assert_message_prettifying(ttt "\n", ttt sss sss sss, 0); +} + +void test_object_message__text_plus_spaces_ending_with_newline_should_be_cleaned_and_newline_must_remain(void){ + assert_message_prettifying(ttt "\n", ttt sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt sss sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt sss sss sss "\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss "\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss "\n", 0); +} + +void test_object_message__spaces_with_newline_at_end_should_be_replaced_with_empty_string(void) +{ + assert_message_prettifying("", sss "\n", 0); + assert_message_prettifying("", sss sss "\n", 0); + assert_message_prettifying("", sss sss sss "\n", 0); + assert_message_prettifying("", sss sss sss sss "\n", 0); +} + +void test_object_message__spaces_without_newline_at_end_should_be_replaced_with_empty_string(void) +{ + assert_message_prettifying("", "", 0); + assert_message_prettifying("", sss sss, 0); + assert_message_prettifying("", sss sss sss, 0); + assert_message_prettifying("", sss sss sss sss, 0); +} + +void test_object_message__consecutive_text_lines_should_be_unchanged(void) +{ + assert_message_prettifying(ttt ttt "\n" ttt "\n", ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n" ttt ttt "\n", ttt ttt "\n\n" ttt "\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt ttt "\n\n" ttt "\n", ttt "\n" ttt ttt "\n\n" ttt "\n", 0); +} + +void test_object_message__strip_comments(void) +{ + assert_message_prettifying("", "# comment", 1); + assert_message_prettifying("", "# comment\n", 1); + assert_message_prettifying("", "# comment \n", 1); + + assert_message_prettifying(ttt "\n", ttt "\n" "# comment\n", 1); + assert_message_prettifying(ttt "\n", "# comment\n" ttt "\n", 1); + assert_message_prettifying(ttt "\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 1); +} + +void test_object_message__keep_comments(void) +{ + assert_message_prettifying("# comment\n", "# comment", 0); + assert_message_prettifying("# comment\n", "# comment\n", 0); + assert_message_prettifying("# comment\n", "# comment \n", 0); + + assert_message_prettifying(ttt "\n" "# comment\n", ttt "\n" "# comment\n", 0); + assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0); +} diff --git a/tests-clar/object/raw/chars.c b/tests-clar/object/raw/chars.c new file mode 100644 index 000000000..206bf7119 --- /dev/null +++ b/tests-clar/object/raw/chars.c @@ -0,0 +1,41 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_chars__find_invalid_chars_in_oid(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + char in[41] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0"; + unsigned int i; + + for (i = 0; i < 256; i++) { + in[38] = (char)i; + if (git__fromhex(i) >= 0) { + exp[19] = (unsigned char)(git__fromhex(i) << 4); + cl_git_pass(git_oid_fromstr(&out, in)); + cl_assert(memcmp(out.id, exp, sizeof(out.id)) == 0); + } else { + cl_git_fail(git_oid_fromstr(&out, in)); + } + } +} + +void test_object_raw_chars__build_valid_oid_from_raw_bytes(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + git_oid_fromraw(&out, exp); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); +} diff --git a/tests-clar/object/raw/compare.c b/tests-clar/object/raw/compare.c new file mode 100644 index 000000000..1c9ce4b81 --- /dev/null +++ b/tests-clar/object/raw/compare.c @@ -0,0 +1,124 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_compare__succeed_on_copy_oid(void) +{ + git_oid a, b; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + memset(&b, 0, sizeof(b)); + git_oid_fromraw(&a, exp); + git_oid_cpy(&b, &a); + cl_git_pass(memcmp(a.id, exp, sizeof(a.id))); +} + +void test_object_raw_compare__succeed_on_oid_comparison_lesser(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + unsigned char b_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xf0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, b_in); + cl_assert(git_oid_cmp(&a, &b) < 0); +} + +void test_object_raw_compare__succeed_on_oid_comparison_equal(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, a_in); + cl_assert(git_oid_cmp(&a, &b) == 0); +} + +void test_object_raw_compare__succeed_on_oid_comparison_greater(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + unsigned char b_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xd0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, b_in); + cl_assert(git_oid_cmp(&a, &b) > 0); +} + +void test_object_raw_compare__compare_fmt_oids(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 1]; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* Format doesn't touch the last byte */ + out[GIT_OID_HEXSZ] = 'Z'; + git_oid_fmt(out, &in); + cl_assert(out[GIT_OID_HEXSZ] == 'Z'); + + /* Format produced the right result */ + out[GIT_OID_HEXSZ] = '\0'; + cl_assert_equal_s(exp, out); +} + +void test_object_raw_compare__compare_allocfmt_oids(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char *out; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + out = git_oid_allocfmt(&in); + cl_assert(out); + cl_assert_equal_s(exp, out); + git__free(out); +} + +void test_object_raw_compare__compare_pathfmt_oids(void) +{ + const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 2]; + + cl_git_pass(git_oid_fromstr(&in, exp1)); + + /* Format doesn't touch the last byte */ + out[GIT_OID_HEXSZ + 1] = 'Z'; + git_oid_pathfmt(out, &in); + cl_assert(out[GIT_OID_HEXSZ + 1] == 'Z'); + + /* Format produced the right result */ + out[GIT_OID_HEXSZ + 1] = '\0'; + cl_assert_equal_s(exp2, out); +} diff --git a/tests-clar/object/raw/convert.c b/tests-clar/object/raw/convert.c new file mode 100644 index 000000000..7f310ddf0 --- /dev/null +++ b/tests-clar/object/raw/convert.c @@ -0,0 +1,75 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_convert__succeed_on_oid_to_string_conversion(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 1]; + char *str; + int i; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* NULL buffer pointer, returns static empty string */ + str = git_oid_tostr(NULL, sizeof(out), &in); + cl_assert(str && *str == '\0' && str != out); + + /* zero buffer size, returns static empty string */ + str = git_oid_tostr(out, 0, &in); + cl_assert(str && *str == '\0' && str != out); + + /* NULL oid pointer, returns static empty string */ + str = git_oid_tostr(out, sizeof(out), NULL); + cl_assert(str && *str == '\0' && str != out); + + /* n == 1, returns out as an empty string */ + str = git_oid_tostr(out, 1, &in); + cl_assert(str && *str == '\0' && str == out); + + for (i = 1; i < GIT_OID_HEXSZ; i++) { + out[i+1] = 'Z'; + str = git_oid_tostr(out, i+1, &in); + /* returns out containing c-string */ + cl_assert(str && str == out); + /* must be '\0' terminated */ + cl_assert(*(str+i) == '\0'); + /* must not touch bytes past end of string */ + cl_assert(*(str+(i+1)) == 'Z'); + /* i == n-1 charaters of string */ + cl_git_pass(strncmp(exp, out, i)); + } + + /* returns out as hex formatted c-string */ + str = git_oid_tostr(out, sizeof(out), &in); + cl_assert(str && str == out && *(str+GIT_OID_HEXSZ) == '\0'); + cl_assert_equal_s(exp, out); +} + +void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ + char *str; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* place some tail material */ + big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */ + big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */ + big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */ + big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */ + + /* returns big as hex formatted c-string */ + str = git_oid_tostr(big, sizeof(big), &in); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ) == '\0'); + cl_assert_equal_s(exp, big); + + /* check tail material is untouched */ + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X'); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y'); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); +} diff --git a/tests/t01-data.h b/tests-clar/object/raw/data.h similarity index 99% rename from tests/t01-data.h rename to tests-clar/object/raw/data.h index 268269d69..cf23819f1 100644 --- a/tests/t01-data.h +++ b/tests-clar/object/raw/data.h @@ -95,8 +95,11 @@ static unsigned char tag_data[] = { 0x2e, 0x30, 0x2e, 0x31, 0x0a, }; +/* + * Dummy data + */ static unsigned char zero_data[] = { - 0x00 /* dummy data */ + 0x00, }; static unsigned char one_data[] = { @@ -258,7 +261,7 @@ static unsigned char some_data[] = { }; /* - * Sha1 IDS + * SHA1 Hashes */ static char *commit_id = "3d7f8a6af076c8c3f20071a8935cdbe8228594d1"; static char *tree_id = "dff2da90b254e1beb889d1f1f1288be1803782df"; @@ -269,7 +272,7 @@ static char *two_id = "78981922613b2afb6025042ff6bd878ac1994e85"; static char *some_id = "fd8430bc864cfcd5f10e5590f8a447e01b942bfe"; /* - * In memory objects + * In-memory objects */ static git_rawobj tree_obj = { tree_data, @@ -318,5 +321,3 @@ static git_rawobj junk_obj = { 0, GIT_OBJ_BAD }; - - diff --git a/tests-clar/object/raw/fromstr.c b/tests-clar/object/raw/fromstr.c new file mode 100644 index 000000000..8c11c105f --- /dev/null +++ b/tests-clar/object/raw/fromstr.c @@ -0,0 +1,30 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_fromstr__fail_on_invalid_oid_string(void) +{ + git_oid out; + cl_git_fail(git_oid_fromstr(&out, "")); + cl_git_fail(git_oid_fromstr(&out, "moo")); + cl_git_fail(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez")); +} + +void test_object_raw_fromstr__succeed_on_valid_oid_string(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + + cl_git_pass(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0")); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); + + cl_git_pass(git_oid_fromstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0")); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); + +} diff --git a/tests-clar/object/raw/hash.c b/tests-clar/object/raw/hash.c new file mode 100644 index 000000000..4b8b1b74c --- /dev/null +++ b/tests-clar/object/raw/hash.c @@ -0,0 +1,166 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +#include "data.h" + +static void hash_object_pass(git_oid *oid, git_rawobj *obj) +{ + cl_git_pass(git_odb_hash(oid, obj->data, obj->len, obj->type)); +} +static void hash_object_fail(git_oid *oid, git_rawobj *obj) +{ + cl_git_fail(git_odb_hash(oid, obj->data, obj->len, obj->type)); +} + +static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511"; +static char *hello_text = "hello world\n"; + +static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"; +static char *bye_text = "bye world\n"; + +void test_object_raw_hash__hash_by_blocks(void) +{ + git_hash_ctx *ctx; + git_oid id1, id2; + + cl_assert((ctx = git_hash_new_ctx()) != NULL); + + /* should already be init'd */ + git_hash_update(ctx, hello_text, strlen(hello_text)); + git_hash_final(&id2, ctx); + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + + /* reinit should permit reuse */ + git_hash_init(ctx); + git_hash_update(ctx, bye_text, strlen(bye_text)); + git_hash_final(&id2, ctx); + cl_git_pass(git_oid_fromstr(&id1, bye_id)); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + + git_hash_free_ctx(ctx); +} + +void test_object_raw_hash__hash_buffer_in_single_call(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + git_hash_buf(&id2, hello_text, strlen(hello_text)); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_vector(void) +{ + git_oid id1, id2; + git_buf_vec vec[2]; + + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + + vec[0].data = hello_text; + vec[0].len = 4; + vec[1].data = hello_text+4; + vec[1].len = strlen(hello_text)-4; + + git_hash_vec(&id2, vec, 2); + + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_junk_data(void) +{ + git_oid id, id_zero; + + cl_git_pass(git_oid_fromstr(&id_zero, zero_id)); + + /* invalid types: */ + junk_obj.data = some_data; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJ__EXT1; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJ__EXT2; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJ_OFS_DELTA; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJ_REF_DELTA; + hash_object_fail(&id, &junk_obj); + + /* data can be NULL only if len is zero: */ + junk_obj.type = GIT_OBJ_BLOB; + junk_obj.data = NULL; + hash_object_pass(&id, &junk_obj); + cl_assert(git_oid_cmp(&id, &id_zero) == 0); + + junk_obj.len = 1; + hash_object_fail(&id, &junk_obj); +} + +void test_object_raw_hash__hash_commit_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, commit_id)); + hash_object_pass(&id2, &commit_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_tree_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, tree_id)); + hash_object_pass(&id2, &tree_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_tag_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, tag_id)); + hash_object_pass(&id2, &tag_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_zero_length_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, zero_id)); + hash_object_pass(&id2, &zero_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_one_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, one_id)); + hash_object_pass(&id2, &one_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_two_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, two_id)); + hash_object_pass(&id2, &two_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_multi_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, some_id)); + hash_object_pass(&id2, &some_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} diff --git a/tests-clar/object/raw/short.c b/tests-clar/object/raw/short.c new file mode 100644 index 000000000..14b1ae219 --- /dev/null +++ b/tests-clar/object/raw/short.c @@ -0,0 +1,94 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +void test_object_raw_short__oid_shortener_no_duplicates(void) +{ + git_oid_shorten *os; + int min_len; + + os = git_oid_shorten_new(0); + cl_assert(os != NULL); + + git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511"); + git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0"); + min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + + cl_assert(min_len == GIT_OID_HEXSZ + 1); + + git_oid_shorten_free(os); +} + +void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void) +{ +#define MAX_OIDS 1000 + + git_oid_shorten *os; + char *oids[MAX_OIDS]; + char number_buffer[16]; + git_oid oid; + size_t i, j; + + int min_len = 0, found_collision; + + os = git_oid_shorten_new(0); + cl_assert(os != NULL); + + /* + * Insert in the shortener 1000 unique SHA1 ids + */ + for (i = 0; i < MAX_OIDS; ++i) { + char *oid_text; + + sprintf(number_buffer, "%u", (unsigned int)i); + git_hash_buf(&oid, number_buffer, strlen(number_buffer)); + + oid_text = git__malloc(GIT_OID_HEXSZ + 1); + git_oid_fmt(oid_text, &oid); + oid_text[GIT_OID_HEXSZ] = 0; + + min_len = git_oid_shorten_add(os, oid_text); + cl_assert(min_len >= 0); + + oids[i] = oid_text; + } + + /* + * Compare the first `min_char - 1` characters of each + * SHA1 OID. If the minimizer worked, we should find at + * least one collision + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = 0; j < MAX_OIDS; ++j) { + if (i != j && memcmp(oids[i], oids[j], min_len - 1) == 0) + found_collision = 1; + } + } + cl_assert(found_collision == 1); + + /* + * Compare the first `min_char` characters of each + * SHA1 OID. If the minimizer worked, every single preffix + * should be unique. + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = 0; j < MAX_OIDS; ++j) { + if (i != j && memcmp(oids[i], oids[j], min_len) == 0) + found_collision = 1; + } + } + cl_assert(found_collision == 0); + + /* cleanup */ + for (i = 0; i < MAX_OIDS; ++i) + git__free(oids[i]); + + git_oid_shorten_free(os); + +#undef MAX_OIDS +} diff --git a/tests-clar/object/raw/size.c b/tests-clar/object/raw/size.c new file mode 100644 index 000000000..930c6de23 --- /dev/null +++ b/tests-clar/object/raw/size.c @@ -0,0 +1,13 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_size__validate_oid_size(void) +{ + git_oid out; + cl_assert(20 == GIT_OID_RAWSZ); + cl_assert(40 == GIT_OID_HEXSZ); + cl_assert(sizeof(out) == GIT_OID_RAWSZ); + cl_assert(sizeof(out.id) == GIT_OID_RAWSZ); +} diff --git a/tests-clar/object/raw/type2string.c b/tests-clar/object/raw/type2string.c new file mode 100644 index 000000000..a3585487f --- /dev/null +++ b/tests-clar/object/raw/type2string.c @@ -0,0 +1,54 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +void test_object_raw_type2string__convert_type_to_string(void) +{ + cl_assert_equal_s(git_object_type2string(GIT_OBJ_BAD), ""); + cl_assert_equal_s(git_object_type2string(GIT_OBJ__EXT1), ""); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_COMMIT), "commit"); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_TREE), "tree"); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_BLOB), "blob"); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_TAG), "tag"); + cl_assert_equal_s(git_object_type2string(GIT_OBJ__EXT2), ""); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_OFS_DELTA), "OFS_DELTA"); + cl_assert_equal_s(git_object_type2string(GIT_OBJ_REF_DELTA), "REF_DELTA"); + + cl_assert_equal_s(git_object_type2string(-2), ""); + cl_assert_equal_s(git_object_type2string(8), ""); + cl_assert_equal_s(git_object_type2string(1234), ""); +} + +void test_object_raw_type2string__convert_string_to_type(void) +{ + cl_assert(git_object_string2type(NULL) == GIT_OBJ_BAD); + cl_assert(git_object_string2type("") == GIT_OBJ_BAD); + cl_assert(git_object_string2type("commit") == GIT_OBJ_COMMIT); + cl_assert(git_object_string2type("tree") == GIT_OBJ_TREE); + cl_assert(git_object_string2type("blob") == GIT_OBJ_BLOB); + cl_assert(git_object_string2type("tag") == GIT_OBJ_TAG); + cl_assert(git_object_string2type("OFS_DELTA") == GIT_OBJ_OFS_DELTA); + cl_assert(git_object_string2type("REF_DELTA") == GIT_OBJ_REF_DELTA); + + cl_assert(git_object_string2type("CoMmIt") == GIT_OBJ_BAD); + cl_assert(git_object_string2type("hohoho") == GIT_OBJ_BAD); +} + +void test_object_raw_type2string__check_type_is_loose(void) +{ + cl_assert(git_object_typeisloose(GIT_OBJ_BAD) == 0); + cl_assert(git_object_typeisloose(GIT_OBJ__EXT1) == 0); + cl_assert(git_object_typeisloose(GIT_OBJ_COMMIT) == 1); + cl_assert(git_object_typeisloose(GIT_OBJ_TREE) == 1); + cl_assert(git_object_typeisloose(GIT_OBJ_BLOB) == 1); + cl_assert(git_object_typeisloose(GIT_OBJ_TAG) == 1); + cl_assert(git_object_typeisloose(GIT_OBJ__EXT2) == 0); + cl_assert(git_object_typeisloose(GIT_OBJ_OFS_DELTA) == 0); + cl_assert(git_object_typeisloose(GIT_OBJ_REF_DELTA) == 0); + + cl_assert(git_object_typeisloose(-2) == 0); + cl_assert(git_object_typeisloose(8) == 0); + cl_assert(git_object_typeisloose(1234) == 0); +} diff --git a/tests-clar/object/raw/write.c b/tests-clar/object/raw/write.c new file mode 100644 index 000000000..1b28d0df7 --- /dev/null +++ b/tests-clar/object/raw/write.c @@ -0,0 +1,455 @@ + +#include "clar_libgit2.h" +#include "fileops.h" +#include "odb.h" + +typedef struct object_data { + char *id; /* object id (sha1) */ + char *dir; /* object store (fan-out) directory name */ + char *file; /* object store filename */ +} object_data; + +static const char *odb_dir = "test-objects"; + +void test_body(object_data *d, git_rawobj *o); + + + +// Helpers +static void remove_object_files(object_data *d) +{ + cl_git_pass(p_unlink(d->file)); + cl_git_pass(p_rmdir(d->dir)); + cl_assert(errno != ENOTEMPTY); + cl_git_pass(p_rmdir(odb_dir) < 0); +} + +static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) +{ + git_odb_stream *stream; + 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); + cl_git_pass(error); +} + +static void check_object_files(object_data *d) +{ + cl_assert(git_path_exists(d->dir)); + cl_assert(git_path_exists(d->file)); +} + +static void cmp_objects(git_rawobj *o1, git_rawobj *o2) +{ + cl_assert(o1->type == o2->type); + cl_assert(o1->len == o2->len); + if (o1->len > 0) + cl_assert(memcmp(o1->data, o2->data, o1->len) == 0); +} + +static void make_odb_dir(void) +{ + cl_git_pass(p_mkdir(odb_dir, GIT_OBJECT_DIR_MODE)); +} + + +// Standard test form +void test_body(object_data *d, git_rawobj *o) +{ + git_odb *db; + git_oid id1, id2; + git_odb_object *obj; + + make_odb_dir(); + cl_git_pass(git_odb_open(&db, odb_dir)); + cl_git_pass(git_oid_fromstr(&id1, d->id)); + + streaming_write(&id2, db, o); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + check_object_files(d); + + cl_git_pass(git_odb_read(&obj, db, &id1)); + cmp_objects(&obj->raw, o); + + git_odb_object_free(obj); + git_odb_free(db); + remove_object_files(d); +} + + +void test_object_raw_write__loose_object(void) +{ + object_data commit = { + "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", + "test-objects/3d", + "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", + }; + + unsigned char commit_data[] = { + 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, + 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, + 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, + 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, + 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, + 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, + 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, + 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, + 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, + 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, + 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, + 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, + 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, + 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x3e, 0x0a, + }; + + git_rawobj commit_obj = { + commit_data, + sizeof(commit_data), + GIT_OBJ_COMMIT + }; + + test_body(&commit, &commit_obj); +} + +void test_object_raw_write__loose_tree(void) +{ + static object_data tree = { + "dff2da90b254e1beb889d1f1f1288be1803782df", + "test-objects/df", + "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", + }; + + static unsigned char tree_data[] = { + 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, + 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, + 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, + 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, + 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, + 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, + 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, + 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, + 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, + 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, + 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, + 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, + 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, + 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, + }; + + static git_rawobj tree_obj = { + tree_data, + sizeof(tree_data), + GIT_OBJ_TREE + }; + + test_body(&tree, &tree_obj); +} + +void test_object_raw_write__loose_tag(void) +{ + static object_data tag = { + "09d373e1dfdc16b129ceec6dd649739911541e05", + "test-objects/09", + "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", + }; + + static unsigned char tag_data[] = { + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, + 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, + 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, + 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, + 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, + 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, + 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x0a, + }; + + static git_rawobj tag_obj = { + tag_data, + sizeof(tag_data), + GIT_OBJ_TAG + }; + + + test_body(&tag, &tag_obj); +} + +void test_object_raw_write__zero_length(void) +{ + static object_data zero = { + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "test-objects/e6", + "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", + }; + + static unsigned char zero_data[] = { + 0x00 /* dummy data */ + }; + + static git_rawobj zero_obj = { + zero_data, + 0, + GIT_OBJ_BLOB + }; + + test_body(&zero, &zero_obj); +} + +void test_object_raw_write__one_byte(void) +{ + static object_data one = { + "8b137891791fe96927ad78e64b0aad7bded08bdc", + "test-objects/8b", + "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", + }; + + static unsigned char one_data[] = { + 0x0a, + }; + + static git_rawobj one_obj = { + one_data, + sizeof(one_data), + GIT_OBJ_BLOB + }; + + test_body(&one, &one_obj); +} + +void test_object_raw_write__two_byte(void) +{ + static object_data two = { + "78981922613b2afb6025042ff6bd878ac1994e85", + "test-objects/78", + "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", + }; + + static unsigned char two_data[] = { + 0x61, 0x0a, + }; + + static git_rawobj two_obj = { + two_data, + sizeof(two_data), + GIT_OBJ_BLOB + }; + + test_body(&two, &two_obj); +} + +void test_object_raw_write__several_bytes(void) +{ + static object_data some = { + "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", + "test-objects/fd", + "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", + }; + + static unsigned char some_data[] = { + 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, + 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, + 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, + 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, + 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, + 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, + 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, + 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, + 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, + 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, + 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, + 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, + 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, + 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, + 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, + 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, + 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, + 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, + 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, + 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, + 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, + 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, + 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, + 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, + 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, + 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, + 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, + 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, + 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, + 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, + 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, + 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, + 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, + 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, + 0x0a, + }; + + static git_rawobj some_obj = { + some_data, + sizeof(some_data), + GIT_OBJ_BLOB + }; + + test_body(&some, &some_obj); +} diff --git a/tests-clar/object/tag/peel.c b/tests-clar/object/tag/peel.c new file mode 100644 index 000000000..97c5a7dd3 --- /dev/null +++ b/tests-clar/object/tag/peel.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" +#include "tag.h" + +static git_repository *repo; +static git_tag *tag; +static git_object *target; + +void test_object_tag_peel__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); +} + +void test_object_tag_peel__cleanup(void) +{ + git_tag_free(tag); + git_object_free(target); + git_repository_free(repo); + + cl_fixture_cleanup("testrepo.git"); +} + +static void retrieve_tag_from_oid(git_tag **tag_out, git_repository *repo, const char *sha) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_tag_lookup(tag_out, repo, &oid)); +} + +void test_object_tag_peel__can_peel_to_a_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "7b4384978d2493e851f9cca7858815fac9b10980"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJ_COMMIT); + cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); +} + +void test_object_tag_peel__can_peel_several_nested_tags_to_a_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJ_COMMIT); + cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); +} + +void test_object_tag_peel__can_peel_to_a_non_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJ_BLOB); + cl_git_pass(git_oid_streq(git_object_id(target), "1385f264afb75a56a5bec74243be9b367ba4ca08")); +} diff --git a/tests-clar/object/tag/read.c b/tests-clar/object/tag/read.c new file mode 100644 index 000000000..6a0ad8a23 --- /dev/null +++ b/tests-clar/object/tag/read.c @@ -0,0 +1,130 @@ +#include "clar_libgit2.h" + +#include "tag.h" + +static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; +static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; +static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; +static const char *bad_tag_id = "eda9f45a2a98d4c17a09d681d88569fa4ea91755"; +static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; + +static git_repository *g_repo; + + +// Helpers +static void ensure_tag_pattern_match(git_repository *repo, + const char *pattern, + const size_t expected_matches) +{ + git_strarray tag_list; + int error = 0; + + if ((error = git_tag_list_match(&tag_list, pattern, repo)) < 0) + goto exit; + + if (tag_list.count != expected_matches) + error = GIT_ERROR; + +exit: + git_strarray_free(&tag_list); + cl_git_pass(error); +} + + +// Fixture setup and teardown +void test_object_tag_read__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_read__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +void test_object_tag_read__parse(void) +{ + // read and parse a tag from the repository + git_tag *tag1, *tag2; + git_commit *commit; + git_oid id1, id2, id_commit; + + git_oid_fromstr(&id1, tag1_id); + git_oid_fromstr(&id2, tag2_id); + git_oid_fromstr(&id_commit, tagged_commit); + + cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1)); + + cl_assert_equal_s(git_tag_name(tag1), "test"); + cl_assert(git_tag_type(tag1) == GIT_OBJ_TAG); + + cl_git_pass(git_tag_target((git_object **)&tag2, tag1)); + cl_assert(tag2 != NULL); + + cl_assert(git_oid_cmp(&id2, git_tag_id(tag2)) == 0); + + cl_git_pass(git_tag_target((git_object **)&commit, tag2)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_tag_free(tag1); + git_tag_free(tag2); + git_commit_free(commit); +} + +void test_object_tag_read__list(void) +{ + // list all tag names from the repository + git_strarray tag_list; + + cl_git_pass(git_tag_list(&tag_list, g_repo)); + + cl_assert(tag_list.count == 3); + + git_strarray_free(&tag_list); +} + +void test_object_tag_read__list_pattern(void) +{ + // list all tag names from the repository matching a specified pattern + ensure_tag_pattern_match(g_repo, "", 3); + ensure_tag_pattern_match(g_repo, "*", 3); + ensure_tag_pattern_match(g_repo, "t*", 1); + ensure_tag_pattern_match(g_repo, "*b", 2); + ensure_tag_pattern_match(g_repo, "e", 0); + ensure_tag_pattern_match(g_repo, "e90810b", 1); + ensure_tag_pattern_match(g_repo, "e90810[ab]", 1); +} + +void test_object_tag_read__parse_without_tagger(void) +{ + // read and parse a tag without a tagger field + git_repository *bad_tag_repo; + git_tag *bad_tag; + git_commit *commit; + git_oid id, id_commit; + + // TODO: This is a little messy + cl_git_pass(git_repository_open(&bad_tag_repo, cl_fixture("bad_tag.git"))); + + git_oid_fromstr(&id, bad_tag_id); + git_oid_fromstr(&id_commit, badly_tagged_commit); + + cl_git_pass(git_tag_lookup(&bad_tag, bad_tag_repo, &id)); + cl_assert(bad_tag != NULL); + + cl_assert_equal_s(git_tag_name(bad_tag), "e90810b"); + cl_assert(git_oid_cmp(&id, git_tag_id(bad_tag)) == 0); + cl_assert(bad_tag->tagger == NULL); + + cl_git_pass(git_tag_target((git_object **)&commit, bad_tag)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_tag_free(bad_tag); + git_commit_free(commit); + git_repository_free(bad_tag_repo); +} diff --git a/tests-clar/object/tag/write.c b/tests-clar/object/tag/write.c new file mode 100644 index 000000000..cb196b64e --- /dev/null +++ b/tests-clar/object/tag/write.c @@ -0,0 +1,192 @@ +#include "clar_libgit2.h" + +static const char* tagger_name = "Vicent Marti"; +static const char* tagger_email = "vicent@github.com"; +static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n"; + +static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; +static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; + +static git_repository *g_repo; + +// Fixture setup and teardown +void test_object_tag_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tag_write__basic(void) +{ + // write a tag to the repository and read it again + git_tag *tag; + git_oid target_id, tag_id; + git_signature *tagger; + const git_signature *tagger1; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT)); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass( + git_tag_create(&tag_id, g_repo, + "the-tag", target, tagger, tagger_message, 0) + ); + + git_object_free(target); + git_signature_free(tagger); + + cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id)); + cl_assert(git_oid_cmp(git_tag_target_oid(tag), &target_id) == 0); + + /* Check attributes were set correctly */ + tagger1 = git_tag_tagger(tag); + cl_assert(tagger1 != NULL); + cl_assert_equal_s(tagger1->name, tagger_name); + cl_assert_equal_s(tagger1->email, tagger_email); + cl_assert(tagger1->when.time == 123456789); + cl_assert(tagger1->when.offset == 60); + + cl_assert_equal_s(git_tag_message(tag), tagger_message); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/the-tag")); + cl_assert(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0); + cl_git_pass(git_reference_delete(ref_tag)); + + git_tag_free(tag); +} + +void test_object_tag_write__overwrite(void) +{ + // Attempt to write a tag bearing the same name than an already existing tag + git_oid target_id, tag_id; + git_signature *tagger; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT)); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_fail(git_tag_create( + &tag_id, /* out id */ + g_repo, + "e90810b", + target, + tagger, + tagger_message, + 0)); + + git_object_free(target); + git_signature_free(tagger); + +} + +void test_object_tag_write__replace(void) +{ + // Replace an already existing tag + git_oid target_id, tag_id, old_tag_id; + git_signature *tagger; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT)); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + git_oid_cpy(&old_tag_id, git_reference_oid(ref_tag)); + git_reference_free(ref_tag); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass(git_tag_create( + &tag_id, /* out id */ + g_repo, + "e90810b", + target, + tagger, + tagger_message, + 1)); + + git_object_free(target); + git_signature_free(tagger); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + cl_assert(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0); + cl_assert(git_oid_cmp(git_reference_oid(ref_tag), &old_tag_id) != 0); + + git_reference_free(ref_tag); +} + +void test_object_tag_write__lightweight(void) +{ + // write a lightweight tag to the repository and read it again + git_oid target_id, object_id; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT)); + + cl_git_pass(git_tag_create_lightweight( + &object_id, + g_repo, + "light-tag", + target, + 0)); + + git_object_free(target); + + cl_assert(git_oid_cmp(&object_id, &target_id) == 0); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/light-tag")); + cl_assert(git_oid_cmp(git_reference_oid(ref_tag), &target_id) == 0); + + cl_git_pass(git_tag_delete(g_repo, "light-tag")); + + git_reference_free(ref_tag); +} + +void test_object_tag_write__lightweight_over_existing(void) +{ + // Attempt to write a lightweight tag bearing the same name than an already existing tag + git_oid target_id, object_id, existing_object_id; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT)); + + cl_git_fail(git_tag_create_lightweight( + &object_id, + g_repo, + "e90810b", + target, + 0)); + + git_oid_fromstr(&existing_object_id, tag2_id); + cl_assert(git_oid_cmp(&object_id, &existing_object_id) == 0); + + git_object_free(target); +} + +void test_object_tag_write__delete(void) +{ + // Delete an already existing tag + git_reference *ref_tag; + + cl_git_pass(git_tag_delete(g_repo, "e90810b")); + + cl_git_fail(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + + git_reference_free(ref_tag); +} diff --git a/tests-clar/object/tree/frompath.c b/tests-clar/object/tree/frompath.c new file mode 100644 index 000000000..06c69ac08 --- /dev/null +++ b/tests-clar/object/tree/frompath.c @@ -0,0 +1,81 @@ +#include "clar_libgit2.h" + +static git_repository *repo; +const char *tree_with_subtrees_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; +static git_tree *tree; + +void test_object_tree_frompath__initialize(void) +{ + git_oid id; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_assert(repo != NULL); + + cl_git_pass(git_oid_fromstr(&id, tree_with_subtrees_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &id)); + cl_assert(tree != NULL); +} + +void test_object_tree_frompath__cleanup(void) +{ + git_tree_free(tree); + git_repository_free(repo); + cl_fixture_cleanup("testrepo.git"); +} + +static void assert_tree_from_path(git_tree *root, const char *path, int expected_result, const char *expected_raw_oid) +{ + git_tree *containing_tree = NULL; + + cl_assert(git_tree_get_subtree(&containing_tree, root, path) == expected_result); + + if (containing_tree == NULL && expected_result != 0) + return; + + cl_assert(containing_tree != NULL && expected_result == 0); + + cl_git_pass(git_oid_streq(git_object_id((const git_object *)containing_tree), expected_raw_oid)); + + git_tree_free(containing_tree); +} + +static void assert_tree_from_path_klass(git_tree *root, const char *path, int expected_result, const char *expected_raw_oid) +{ + assert_tree_from_path(root, path, GIT_ERROR, expected_raw_oid); + cl_assert(giterr_last()->klass == expected_result); +} + +void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void) +{ + /* Will return self if given a one path segment... */ + assert_tree_from_path(tree, "README", 0, tree_with_subtrees_oid); + + /* ...even one that lead to a non existent tree entry. */ + assert_tree_from_path(tree, "i-do-not-exist.txt", 0, tree_with_subtrees_oid); + + /* Will return fgh tree oid given this following path... */ + assert_tree_from_path(tree, "ab/de/fgh/1.txt", 0, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54"); + + /* ... and ab tree oid given this one. */ + assert_tree_from_path(tree, "ab/de", 0, "f1425cef211cc08caa31e7b545ffb232acb098c3"); + + /* Will succeed if given a valid path which leads to a tree entry which doesn't exist */ + assert_tree_from_path(tree, "ab/de/fgh/i-do-not-exist.txt", 0, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54"); +} + +void test_object_tree_frompath__fail_when_processing_an_unknown_tree_segment(void) +{ + assert_tree_from_path(tree, "nope/de/fgh/1.txt", GIT_ENOTFOUND, NULL); + assert_tree_from_path(tree, "ab/me-neither/fgh/2.txt", GIT_ENOTFOUND, NULL); +} + +void test_object_tree_frompath__fail_when_processing_an_invalid_path(void) +{ + assert_tree_from_path_klass(tree, "/", GITERR_INVALID, NULL); + assert_tree_from_path_klass(tree, "/ab", GITERR_INVALID, NULL); + assert_tree_from_path_klass(tree, "/ab/de", GITERR_INVALID, NULL); + assert_tree_from_path_klass(tree, "ab/", GITERR_INVALID, NULL); + assert_tree_from_path_klass(tree, "ab//de", GITERR_INVALID, NULL); + assert_tree_from_path_klass(tree, "ab/de/", GITERR_INVALID, NULL); +} diff --git a/tests-clar/object/tree/read.c b/tests-clar/object/tree/read.c new file mode 100644 index 000000000..59a809bf1 --- /dev/null +++ b/tests-clar/object/tree/read.c @@ -0,0 +1,75 @@ +#include "clar_libgit2.h" + +#include "tree.h" + +static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; + +static git_repository *g_repo; + +// Fixture setup and teardown +void test_object_tree_read__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_read__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_object_tree_read__loaded(void) +{ + // acces randomly the entries on a loaded tree + git_oid id; + git_tree *tree; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + cl_assert(git_tree_entry_byname(tree, "README") != NULL); + cl_assert(git_tree_entry_byname(tree, "NOTEXISTS") == NULL); + cl_assert(git_tree_entry_byname(tree, "") == NULL); + cl_assert(git_tree_entry_byindex(tree, 0) != NULL); + cl_assert(git_tree_entry_byindex(tree, 2) != NULL); + cl_assert(git_tree_entry_byindex(tree, 3) == NULL); + cl_assert(git_tree_entry_byindex(tree, (unsigned int)-1) == NULL); + + git_tree_free(tree); +} + +void test_object_tree_read__two(void) +{ + // read a tree from the repository + git_oid id; + git_tree *tree; + const git_tree_entry *entry; + git_object *obj; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + cl_assert(git_tree_entrycount(tree) == 3); + + /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ + cl_assert(git_object_lookup(&obj, g_repo, &id, GIT_OBJ_TREE) == 0); + cl_assert(obj != NULL); + git_object_free(obj); + obj = NULL; + cl_git_fail(git_object_lookup(&obj, g_repo, &id, GIT_OBJ_BLOB)); + cl_assert(obj == NULL); + + entry = git_tree_entry_byname(tree, "README"); + cl_assert(entry != NULL); + + cl_assert_equal_s(git_tree_entry_name(entry), "README"); + + cl_git_pass(git_tree_entry_to_object(&obj, g_repo, entry)); + cl_assert(obj != NULL); + + git_object_free(obj); + git_tree_free(tree); +} diff --git a/tests-clar/object/tree/write.c b/tests-clar/object/tree/write.c new file mode 100644 index 000000000..3911f6f0e --- /dev/null +++ b/tests-clar/object/tree/write.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" + +#include "tree.h" + +static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; +static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; +static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; +static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; + +static git_repository *g_repo; + +// Fixture setup and teardown +void test_object_tree_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tree_write__from_memory(void) +{ + // write a tree from a memory + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, rid, id2; + + git_oid_fromstr(&id, first_tree); + git_oid_fromstr(&id2, second_tree); + git_oid_fromstr(&bid, blob_oid); + + //create a second tree from first tree using `git_treebuilder_insert` on REPOSITORY_FOLDER. + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_treebuilder_create(&builder, tree)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, "", &bid, 0100644)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "/", &bid, 0100644)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", &bid, 0100644)); + + cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); + cl_git_pass(git_treebuilder_write(&rid, g_repo, builder)); + + cl_assert(git_oid_cmp(&rid, &id2) == 0); + + git_treebuilder_free(builder); + git_tree_free(tree); +} + +void test_object_tree_write__subtree(void) +{ + // write a hierarchical tree from a memory + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, subtree_id, id2, id3; + git_oid id_hiearar; + + git_oid_fromstr(&id, first_tree); + git_oid_fromstr(&id2, second_tree); + git_oid_fromstr(&id3, third_tree); + git_oid_fromstr(&bid, blob_oid); + + //create subtree + cl_git_pass(git_treebuilder_create(&builder, NULL)); + cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); + cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder)); + git_treebuilder_free(builder); + + // create parent tree + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_treebuilder_create(&builder, tree)); + cl_git_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); + cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder)); + git_treebuilder_free(builder); + git_tree_free(tree); + + cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0); + + // check data is correct + cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar)); + cl_assert(2 == git_tree_entrycount(tree)); + git_tree_free(tree); +} diff --git a/tests-clar/odb/loose.c b/tests-clar/odb/loose.c new file mode 100644 index 000000000..f95dc28d4 --- /dev/null +++ b/tests-clar/odb/loose.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "posix.h" +#include "loose_data.h" + +static void write_object_files(object_data *d) +{ + int fd; + + if (p_mkdir(d->dir, GIT_OBJECT_DIR_MODE) < 0) + cl_assert(errno == EEXIST); + + cl_assert((fd = p_creat(d->file, S_IREAD | S_IWRITE)) >= 0); + cl_must_pass(p_write(fd, d->bytes, d->blen)); + + p_close(fd); +} + +static void cmp_objects(git_rawobj *o, object_data *d) +{ + cl_assert(o->type == git_object_string2type(d->type)); + cl_assert(o->len == d->dlen); + + if (o->len > 0) + cl_assert(memcmp(o->data, d->data, o->len) == 0); +} + +static void test_read_object(object_data *data) +{ + git_oid id; + git_odb_object *obj; + git_odb *odb; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_read(&obj, odb, &id)); + + cmp_objects((git_rawobj *)&obj->raw, data); + + git_odb_object_free(obj); + git_odb_free(odb); +} + +void test_odb_loose__initialize(void) +{ + cl_must_pass(p_mkdir("test-objects", GIT_OBJECT_DIR_MODE)); +} + +void test_odb_loose__cleanup(void) +{ + cl_fixture_cleanup("test-objects"); +} + +void test_odb_loose__exists(void) +{ + git_oid id, id2; + git_odb *odb; + + write_object_files(&one); + cl_git_pass(git_odb_open(&odb, "test-objects")); + + cl_git_pass(git_oid_fromstr(&id, one.id)); + + cl_assert(git_odb_exists(odb, &id)); + + /* Test for a non-existant object */ + cl_git_pass(git_oid_fromstr(&id2, "8b137891791fe96927ad78e64b0aad7bded08baa")); + cl_assert(!git_odb_exists(odb, &id2)); + + git_odb_free(odb); +} + +void test_odb_loose__simple_reads(void) +{ + test_read_object(&commit); + test_read_object(&tree); + test_read_object(&tag); + test_read_object(&zero); + test_read_object(&one); + test_read_object(&two); + test_read_object(&some); +} diff --git a/tests/t02-data.h b/tests-clar/odb/loose_data.h similarity index 97% rename from tests/t02-data.h rename to tests-clar/odb/loose_data.h index 705a2d7af..c10c9bc7f 100644 --- a/tests/t02-data.h +++ b/tests-clar/odb/loose_data.h @@ -1,5 +1,13 @@ - -static char *odb_dir = "test-objects"; +typedef struct object_data { + unsigned char *bytes; /* (compressed) bytes stored in object store */ + size_t blen; /* length of data in object store */ + char *id; /* object id (sha1) */ + char *type; /* object type */ + char *dir; /* object store (fan-out) directory name */ + char *file; /* object store filename */ + unsigned char *data; /* (uncompressed) object data */ + size_t dlen; /* length of (uncompressed) object data */ +} object_data; /* one == 8b137891791fe96927ad78e64b0aad7bded08bdc */ static unsigned char one_bytes[] = { @@ -512,4 +520,3 @@ static object_data some = { some_data, sizeof(some_data), }; - diff --git a/tests-clar/odb/mixed.c b/tests-clar/odb/mixed.c new file mode 100644 index 000000000..0bd23e157 --- /dev/null +++ b/tests-clar/odb/mixed.c @@ -0,0 +1,24 @@ +#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); +} + +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/t02-oids.h b/tests-clar/odb/pack_data.h similarity index 99% rename from tests/t02-oids.h rename to tests-clar/odb/pack_data.h index 1a5ed5df0..e6371beb1 100644 --- a/tests/t02-oids.h +++ b/tests-clar/odb/pack_data.h @@ -149,4 +149,3 @@ static const char *loose_objects[] = { "a4a7dce85cf63874e984719f4fdd239f5145052f", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }; - diff --git a/tests-clar/odb/packed.c b/tests-clar/odb/packed.c new file mode 100644 index 000000000..4bce41ba0 --- /dev/null +++ b/tests-clar/odb/packed.c @@ -0,0 +1,78 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "pack_data.h" + +static git_odb *_odb; + +void test_odb_packed__initialize(void) +{ + cl_git_pass(git_odb_open(&_odb, cl_fixture("testrepo.git/objects"))); +} + +void test_odb_packed__cleanup(void) +{ + git_odb_free(_odb); +} + +void test_odb_packed__mass_read(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { + git_oid id; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); + cl_assert(git_odb_exists(_odb, &id) == 1); + cl_git_pass(git_odb_read(&obj, _odb, &id)); + + git_odb_object_free(obj); + } +} + +void test_odb_packed__read_header_0(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_otype type; + + cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->raw.len == len); + cl_assert(obj->raw.type == type); + + git_odb_object_free(obj); + } +} + +void test_odb_packed__read_header_1(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_otype type; + + cl_git_pass(git_oid_fromstr(&id, loose_objects[i])); + + cl_assert(git_odb_exists(_odb, &id) == 1); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->raw.len == len); + cl_assert(obj->raw.type == type); + + git_odb_object_free(obj); + } +} + diff --git a/tests-clar/odb/sorting.c b/tests-clar/odb/sorting.c new file mode 100644 index 000000000..bf64f6af4 --- /dev/null +++ b/tests-clar/odb/sorting.c @@ -0,0 +1,71 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" +#include "odb.h" + +typedef struct { + git_odb_backend base; + int position; +} fake_backend; + +static git_odb_backend *new_backend(int position) +{ + fake_backend *b; + + b = git__malloc(sizeof(fake_backend)); + if (b == NULL) + return NULL; + + memset(b, 0x0, sizeof(fake_backend)); + b->position = position; + return (git_odb_backend *)b; +} + +static void check_backend_sorting(git_odb *odb) +{ + unsigned int i; + + for (i = 0; i < odb->backends.length; ++i) { + fake_backend *internal = + *((fake_backend **)git_vector_get(&odb->backends, i)); + + cl_assert(internal != NULL); + cl_assert(internal->position == (int)i); + } +} + +static git_odb *_odb; + +void test_odb_sorting__initialize(void) +{ + cl_git_pass(git_odb_new(&_odb)); +} + +void test_odb_sorting__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; +} + +void test_odb_sorting__basic_backends_sorting(void) +{ + cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1)); + + check_backend_sorting(_odb); +} + +void test_odb_sorting__alternate_backends_sorting(void) +{ + cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 5)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 3)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(5), 4)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(7), 1)); + + check_backend_sorting(_odb); +} diff --git a/tests-clar/refs/branches/create.c b/tests-clar/refs/branches/create.c new file mode 100644 index 000000000..ad7e1fd2c --- /dev/null +++ b/tests-clar/refs/branches/create.c @@ -0,0 +1,113 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "branch.h" + +static git_repository *repo; +static git_oid branch_target_oid; +static git_object *target; + +void test_refs_branches_create__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); +} + +void test_refs_branches_create__cleanup(void) +{ + git_object_free(target); + git_repository_free(repo); + + cl_fixture_cleanup("testrepo.git"); +} + +static void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(object_out, repo, &oid, GIT_OBJ_ANY)); +} + +static void retrieve_known_commit(git_object **object, git_repository *repo) +{ + retrieve_target_from_oid(object, repo, "e90810b8df3e80c413d903f631643c716887138d"); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_branches_create__can_create_a_local_branch(void) +{ + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); +} + +void test_refs_branches_create__creating_a_local_branch_triggers_the_creation_of_a_new_direct_reference(void) +{ + git_reference *branch; + + retrieve_known_commit(&target, repo); + + cl_git_fail(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); + + cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + + cl_git_pass(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); + cl_assert(git_reference_type(branch) == GIT_REF_OID); + + git_reference_free(branch); +} + +void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) +{ + retrieve_known_commit(&target, repo); + + cl_git_fail(git_branch_create(&branch_target_oid, repo, "br2", target, 0)); +} + +void test_refs_branches_create__can_force_create_over_an_existing_branch(void) +{ + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_create(&branch_target_oid, repo, "br2", target, 1)); + cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); +} + +void test_refs_branches_create__can_not_create_a_branch_pointing_at_an_object_unknown_from_the_repository(void) +{ + git_repository *repo2; + + /* Open another instance of the same repository */ + cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); + + /* Retrieve a commit object from this different repository */ + retrieve_known_commit(&target, repo2); + + cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + + git_repository_free(repo2); +} + +void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void) +{ + /* b25fa35 is a tag, pointing to another tag which points to a commit */ + retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + + cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_streq(&branch_target_oid, "e90810b8df3e80c413d903f631643c716887138d")); +} + +void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void) +{ + /* 53fc32d is the tree of commit e90810b */ + retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); + + cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + git_object_free(target); + + /* 521d87c is an annotated tag pointing to a blob */ + retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); + + cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); +} diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c new file mode 100644 index 000000000..03d3c56d7 --- /dev/null +++ b/tests-clar/refs/branches/delete.c @@ -0,0 +1,91 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "branch.h" + +static git_repository *repo; +static git_reference *fake_remote; + +void test_refs_branches_delete__initialize(void) +{ + git_oid id; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_reference_create_oid(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0)); +} + +void test_refs_branches_delete__cleanup(void) +{ + git_reference_free(fake_remote); + git_repository_free(repo); + + cl_fixture_cleanup("testrepo.git"); +} + +void test_refs_branches_delete__can_not_delete_a_non_existing_branch(void) +{ + cl_git_fail(git_branch_delete(repo, "i-am-not-a-local-branch", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(repo, "neither/a-remote-one", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void) +{ + git_reference *head; + + /* Ensure HEAD targets the local master branch */ + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + cl_assert(strcmp("refs/heads/master", git_reference_target(head)) == 0); + git_reference_free(head); + + cl_git_fail(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_delete__can_not_delete_a_branch_if_HEAD_is_missing(void) +{ + git_reference *head; + + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + git_reference_delete(head); + + cl_git_fail(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void) +{ + git_reference *master, *head; + + /* Detach HEAD and make it target the commit that "master" points to */ + cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); + cl_git_pass(git_reference_create_oid(&head, repo, "HEAD", git_reference_oid(master), 1)); + git_reference_free(head); + git_reference_free(master); + + cl_git_pass(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_delete__can_delete_a_local_branch(void) +{ + cl_git_pass(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_delete__can_delete_a_remote_branch(void) +{ + cl_git_pass(git_branch_delete(repo, "nulltoken/master", GIT_BRANCH_REMOTE)); +} + +static void assert_non_exisitng_branch_removal(const char *branch_name, git_branch_t branch_type) +{ + int error; + error = git_branch_delete(repo, branch_name, branch_type); + + cl_git_fail(error); + cl_assert_equal_i(GIT_ENOTFOUND, error); +} + +void test_refs_branches_delete__deleting_a_non_existing_branch_returns_ENOTFOUND(void) +{ + assert_non_exisitng_branch_removal("i-do-not-locally-exist", GIT_BRANCH_LOCAL); + assert_non_exisitng_branch_removal("neither/remotely", GIT_BRANCH_REMOTE); +} diff --git a/tests-clar/refs/branches/listall.c b/tests-clar/refs/branches/listall.c new file mode 100644 index 000000000..0a5634fb4 --- /dev/null +++ b/tests-clar/refs/branches/listall.c @@ -0,0 +1,78 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "branch.h" + +static git_repository *repo; +static git_strarray branch_list; +static git_reference *fake_remote; + +void test_refs_branches_listall__initialize(void) +{ + git_oid id; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_reference_create_oid(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0)); +} + +void test_refs_branches_listall__cleanup(void) +{ + git_strarray_free(&branch_list); + git_reference_free(fake_remote); + git_repository_free(repo); + + cl_fixture_cleanup("testrepo.git"); +} + +static void assert_retrieval(unsigned int flags, unsigned int expected_count) +{ + cl_git_pass(git_branch_list(&branch_list, repo, flags)); + + cl_assert_equal_i(expected_count, branch_list.count); +} + +void test_refs_branches_listall__retrieve_all_branches(void) +{ + assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 6 + 1); +} + +void test_refs_branches_listall__retrieve_remote_branches(void) +{ + assert_retrieval(GIT_BRANCH_REMOTE, 1); +} + +void test_refs_branches_listall__retrieve_local_branches(void) +{ + assert_retrieval(GIT_BRANCH_LOCAL, 6); +} + +static void assert_branch_list_contains(git_strarray *branches, const char* expected_branch_name) +{ + unsigned int i; + + for (i = 0; i < branches->count; i++) { + if (strcmp(expected_branch_name, branches->strings[i]) == 0) + return; + } + + cl_fail("expected branch not found in list."); +} + +/* + * $ git branch -r + * nulltoken/HEAD -> nulltoken/master + * nulltoken/master + */ +void test_refs_branches_listall__retrieve_remote_symbolic_HEAD_when_present(void) +{ + git_reference_free(fake_remote); + cl_git_pass(git_reference_create_symbolic(&fake_remote, repo, "refs/remotes/nulltoken/HEAD", "refs/remotes/nulltoken/master", 0)); + + cl_git_pass(git_branch_list(&branch_list, repo, GIT_BRANCH_REMOTE)); + + cl_assert_equal_i(2, branch_list.count); + assert_branch_list_contains(&branch_list, "refs/remotes/nulltoken/HEAD"); + assert_branch_list_contains(&branch_list, "refs/remotes/nulltoken/master"); +} diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c new file mode 100644 index 000000000..242e5cd01 --- /dev/null +++ b/tests-clar/refs/branches/move.c @@ -0,0 +1,72 @@ +#include "clar_libgit2.h" +#include "branch.h" + +static git_repository *repo; + +void test_refs_branches_move__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); +} + +void test_refs_branches_move__cleanup(void) +{ + git_repository_free(repo); + + cl_fixture_cleanup("testrepo.git"); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_branches_move__can_move_a_local_branch(void) +{ + cl_git_pass(git_branch_move(repo, "br2", NEW_BRANCH_NAME, 0)); +} + +void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) +{ + /* Downward */ + cl_git_pass(git_branch_move(repo, "br2", "somewhere/" NEW_BRANCH_NAME, 0)); + + /* Upward */ + cl_git_pass(git_branch_move(repo, "somewhere/" NEW_BRANCH_NAME, "br2", 0)); +} + +void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) +{ + /* Downward */ + cl_git_pass(git_branch_move(repo, "br2", "br2/" NEW_BRANCH_NAME, 0)); + + /* Upward */ + cl_git_pass(git_branch_move(repo, "br2/" NEW_BRANCH_NAME, "br2", 0)); +} + +void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) +{ + cl_git_fail(git_branch_move(repo, "br2", "master", 0)); +} + +void test_refs_branches_move__can_not_move_a_non_existing_branch(void) +{ + cl_git_fail(git_branch_move(repo, "i-am-no-branch", NEW_BRANCH_NAME, 0)); +} + +void test_refs_branches_move__can_force_move_over_an_existing_branch(void) +{ + cl_git_pass(git_branch_move(repo, "br2", "master", 1)); +} + +void test_refs_branches_move__can_not_move_a_branch_through_its_canonical_name(void) +{ + cl_git_fail(git_branch_move(repo, "refs/heads/br2", NEW_BRANCH_NAME, 1)); +} + +void test_refs_branches_move__moving_a_non_exisiting_branch_returns_ENOTFOUND(void) +{ + int error; + + error = git_branch_move(repo, "where/am/I", NEW_BRANCH_NAME, 0); + cl_git_fail(error); + + cl_assert_equal_i(GIT_ENOTFOUND, error); +} diff --git a/tests-clar/refs/crashes.c b/tests-clar/refs/crashes.c new file mode 100644 index 000000000..e1b289ace --- /dev/null +++ b/tests-clar/refs/crashes.c @@ -0,0 +1,17 @@ +#include "clar_libgit2.h" + +void test_refs_crashes__double_free(void) +{ + git_repository *repo; + git_reference *ref, *ref2; + const char *REFNAME = "refs/heads/xxx"; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_reference_create_symbolic(&ref, repo, REFNAME, "refs/heads/master", 0)); + cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME)); + cl_git_pass(git_reference_delete(ref)); + /* reference is gone from disk, so reloading it will fail */ + cl_git_fail(git_reference_reload(ref2)); + + git_repository_free(repo); +} diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c new file mode 100644 index 000000000..dde4c5745 --- /dev/null +++ b/tests-clar/refs/create.c @@ -0,0 +1,149 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *current_head_target = "refs/heads/master"; + +static git_repository *g_repo; + +void test_refs_create__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_create__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_create__symbolic(void) +{ + // create a new symbolic reference + git_reference *new_reference, *looked_up_ref, *resolved_ref; + git_repository *repo2; + git_oid id; + git_buf ref_path = GIT_BUF_INIT; + + const char *new_head_tracker = "another-head-tracker"; + + git_oid_fromstr(&id, current_master_tip); + + /* Retrieve the physical path to the symbolic ref for further cleaning */ + cl_git_pass(git_buf_joinpath(&ref_path, g_repo->path_repository, new_head_tracker)); + git_buf_free(&ref_path); + + /* Create and write the new symbolic reference */ + cl_git_pass(git_reference_create_symbolic(&new_reference, g_repo, new_head_tracker, current_head_target, 0)); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REF_SYMBOLIC); + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head_tracker); + + /* ...peeled.. */ + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID); + + /* ...and that it points to the current master tip */ + cl_assert(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); + + /* Similar test with a fresh new repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); + + git_repository_free(repo2); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); +} + +void test_refs_create__deep_symbolic(void) +{ + // create a deep symbolic reference + git_reference *new_reference, *looked_up_ref, *resolved_ref; + git_oid id; + git_buf ref_path = GIT_BUF_INIT; + + const char *new_head_tracker = "deep/rooted/tracker"; + + git_oid_fromstr(&id, current_master_tip); + + cl_git_pass(git_buf_joinpath(&ref_path, g_repo->path_repository, new_head_tracker)); + cl_git_pass(git_reference_create_symbolic(&new_reference, g_repo, new_head_tracker, current_head_target, 0)); + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); + git_buf_free(&ref_path); +} + +void test_refs_create__oid(void) +{ + // create a new OID reference + git_reference *new_reference, *looked_up_ref; + git_repository *repo2; + git_oid id; + git_buf ref_path = GIT_BUF_INIT; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, current_master_tip); + + /* Retrieve the physical path to the symbolic ref for further cleaning */ + cl_git_pass(git_buf_joinpath(&ref_path, g_repo->path_repository, new_head)); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create_oid(&new_reference, g_repo, new_head, &id, 0)); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REF_OID); + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head); + + /* ...and that it points to the current master tip */ + cl_assert(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); + git_reference_free(looked_up_ref); + + /* Similar test with a fresh new repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); + cl_assert(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); + + git_repository_free(repo2); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); + git_buf_free(&ref_path); +} + +void test_refs_create__oid_unknown(void) +{ + // Can not create a new OID reference which targets at an unknown id + git_reference *new_reference, *looked_up_ref; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + + /* Create and write the new object id reference */ + cl_git_fail(git_reference_create_oid(&new_reference, g_repo, new_head, &id, 0)); + + /* Ensure the reference can't be looked-up... */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head)); +} diff --git a/tests-clar/refs/delete.c b/tests-clar/refs/delete.c new file mode 100644 index 000000000..912f41456 --- /dev/null +++ b/tests-clar/refs/delete.c @@ -0,0 +1,85 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *packed_test_head_name = "refs/heads/packed-test"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; + +static git_repository *g_repo; + + + +void test_refs_delete__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_delete__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_delete__packed_loose(void) +{ + // deleting a ref which is both packed and loose should remove both tracks in the filesystem + git_reference *looked_up_ref, *another_looked_up_ref; + git_buf temp_path = GIT_BUF_INIT; + + /* Ensure the loose reference exists on the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, packed_test_head_name)); + cl_assert(git_path_exists(temp_path.ptr)); + + /* Lookup the reference */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's the loose version that has been found */ + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + + /* Now that the reference is deleted... */ + cl_git_pass(git_reference_delete(looked_up_ref)); + + /* Looking up the reference once again should not retrieve it */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure the loose reference doesn't exist any longer on the file system */ + cl_assert(!git_path_exists(temp_path.ptr)); + + git_reference_free(another_looked_up_ref); + git_buf_free(&temp_path); +} + +void test_refs_delete__packed_only(void) +{ + // can delete a just packed reference + git_reference *ref; + git_oid id; + const char *new_ref = "refs/heads/new_ref"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &id, 0)); + git_reference_free(ref); + + /* Lookup the reference */ + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); + + /* Ensure it's a loose reference */ + cl_assert(git_reference_is_packed(ref) == 0); + + /* Pack all existing references */ + cl_git_pass(git_reference_packall(g_repo)); + + /* Reload the reference from disk */ + cl_git_pass(git_reference_reload(ref)); + + /* Ensure it's a packed reference */ + cl_assert(git_reference_is_packed(ref) == 1); + + /* This should pass */ + cl_git_pass(git_reference_delete(ref)); +} diff --git a/tests-clar/refs/list.c b/tests-clar/refs/list.c new file mode 100644 index 000000000..2a7b157ca --- /dev/null +++ b/tests-clar/refs/list.c @@ -0,0 +1,53 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static git_repository *g_repo; + + + +void test_refs_list__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_list__all(void) +{ + // try to list all the references in our test repo + git_strarray ref_list; + + cl_git_pass(git_reference_list(&ref_list, g_repo, GIT_REF_LISTALL)); + + /*{ + unsigned short i; + for (i = 0; i < ref_list.count; ++i) + printf("# %s\n", ref_list.strings[i]); + }*/ + + /* We have exactly 9 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(ref_list.count == 9); + + git_strarray_free(&ref_list); +} + +void test_refs_list__symbolic_only(void) +{ + // try to list only the symbolic references + git_strarray ref_list; + + cl_git_pass(git_reference_list(&ref_list, g_repo, GIT_REF_SYMBOLIC)); + cl_assert(ref_list.count == 0); /* no symrefs in the test repo */ + + git_strarray_free(&ref_list); +} diff --git a/tests-clar/refs/listall.c b/tests-clar/refs/listall.c new file mode 100644 index 000000000..7f1de74cc --- /dev/null +++ b/tests-clar/refs/listall.c @@ -0,0 +1,36 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static git_repository *repo; +static git_strarray ref_list; + +static void ensure_no_refname_starts_with_a_forward_slash(const char *path) +{ + size_t i; + + cl_git_pass(git_repository_open(&repo, path)); + cl_git_pass(git_reference_list(&ref_list, repo, GIT_REF_LISTALL)); + + cl_assert(ref_list.count > 0); + + for (i = 0; i < ref_list.count; i++) + cl_assert(git__prefixcmp(ref_list.strings[i], "/") != 0); + + git_strarray_free(&ref_list); + git_repository_free(repo); +} + +void test_refs_listall__from_repository_opened_through_workdir_path(void) +{ + cl_fixture_sandbox("status"); + cl_git_pass(p_rename("status/.gitted", "status/.git")); + + ensure_no_refname_starts_with_a_forward_slash("status"); + + cl_fixture_cleanup("status"); +} + +void test_refs_listall__from_repository_opened_through_gitdir_path(void) +{ + ensure_no_refname_starts_with_a_forward_slash(cl_fixture("testrepo.git")); +} diff --git a/tests-clar/refs/lookup.c b/tests-clar/refs/lookup.c new file mode 100644 index 000000000..ab563ac2b --- /dev/null +++ b/tests-clar/refs/lookup.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *g_repo; + +void test_refs_lookup__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_lookup__with_resolve(void) +{ + git_reference *a, *b, *temp; + + cl_git_pass(git_reference_lookup(&temp, g_repo, "HEAD")); + cl_git_pass(git_reference_resolve(&a, temp)); + git_reference_free(temp); + + cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD", 5)); + cl_assert(git_reference_cmp(a, b) == 0); + git_reference_free(b); + + cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "head-tracker", 5)); + cl_assert(git_reference_cmp(a, b) == 0); + git_reference_free(b); + + git_reference_free(a); +} + +void test_refs_lookup__oid(void) +{ + git_oid tag, expected; + + cl_git_pass(git_reference_name_to_oid(&tag, g_repo, "refs/tags/point_to_blob")); + cl_git_pass(git_oid_fromstr(&expected, "1385f264afb75a56a5bec74243be9b367ba4ca08")); + cl_assert(git_oid_cmp(&tag, &expected) == 0); +} diff --git a/tests-clar/refs/normalize.c b/tests-clar/refs/normalize.c new file mode 100644 index 000000000..135d0a9b6 --- /dev/null +++ b/tests-clar/refs/normalize.c @@ -0,0 +1,200 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + + +// Helpers +static void ensure_refname_normalized(int is_oid_ref, + const char *input_refname, + const char *expected_refname) +{ + char buffer_out[GIT_REFNAME_MAX]; + + if (is_oid_ref) + cl_git_pass(git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname)); + else + cl_git_pass(git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname)); + + if (expected_refname) + cl_assert(0 == strcmp(buffer_out, expected_refname)); +} + +static void ensure_refname_invalid(int is_oid_ref, const char *input_refname) +{ + char buffer_out[GIT_REFNAME_MAX]; + + if (is_oid_ref) + cl_git_fail(git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname)); + else + cl_git_fail(git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname)); +} + +#define OID_REF 1 +#define SYM_REF 0 + + + +void test_refs_normalize__direct(void) +{ + // normalize a direct (OID) reference name + ensure_refname_invalid(OID_REF, "a"); + ensure_refname_invalid(OID_REF, ""); + ensure_refname_invalid(OID_REF, "refs/heads/a/"); + ensure_refname_invalid(OID_REF, "refs/heads/a."); + ensure_refname_invalid(OID_REF, "refs/heads/a.lock"); + ensure_refname_normalized(OID_REF, "refs/dummy/a", NULL); + ensure_refname_normalized(OID_REF, "refs/stash", NULL); + ensure_refname_normalized(OID_REF, "refs/tags/a", "refs/tags/a"); + ensure_refname_normalized(OID_REF, "refs/heads/a/b", "refs/heads/a/b"); + ensure_refname_normalized(OID_REF, "refs/heads/a./b", "refs/heads/a./b"); + ensure_refname_invalid(OID_REF, "refs/heads/foo?bar"); + ensure_refname_invalid(OID_REF, "refs/heads\foo"); + ensure_refname_normalized(OID_REF, "refs/heads/v@ation", "refs/heads/v@ation"); + ensure_refname_normalized(OID_REF, "refs///heads///a", "refs/heads/a"); + ensure_refname_invalid(OID_REF, "refs/heads/.a/b"); + ensure_refname_invalid(OID_REF, "refs/heads/foo/../bar"); + ensure_refname_invalid(OID_REF, "refs/heads/foo..bar"); + ensure_refname_invalid(OID_REF, "refs/heads/./foo"); + ensure_refname_invalid(OID_REF, "refs/heads/v@{ation"); +} + +void test_refs_normalize__symbolic(void) +{ + // normalize a symbolic reference name + ensure_refname_normalized(SYM_REF, "a", "a"); + ensure_refname_normalized(SYM_REF, "a/b", "a/b"); + ensure_refname_normalized(SYM_REF, "refs///heads///a", "refs/heads/a"); + ensure_refname_invalid(SYM_REF, ""); + ensure_refname_invalid(SYM_REF, "heads\foo"); +} + +/* Ported from JGit, BSD licence. + * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 */ +void test_refs_normalize__jgit_suite(void) +{ + // tests borrowed from JGit + +/* EmptyString */ + ensure_refname_invalid(SYM_REF, ""); + ensure_refname_invalid(SYM_REF, "/"); + +/* MustHaveTwoComponents */ + ensure_refname_invalid(OID_REF, "master"); + ensure_refname_normalized(SYM_REF, "heads/master", "heads/master"); + +/* ValidHead */ + + ensure_refname_normalized(SYM_REF, "refs/heads/master", "refs/heads/master"); + ensure_refname_normalized(SYM_REF, "refs/heads/pu", "refs/heads/pu"); + ensure_refname_normalized(SYM_REF, "refs/heads/z", "refs/heads/z"); + ensure_refname_normalized(SYM_REF, "refs/heads/FoO", "refs/heads/FoO"); + +/* ValidTag */ + ensure_refname_normalized(SYM_REF, "refs/tags/v1.0", "refs/tags/v1.0"); + +/* NoLockSuffix */ + ensure_refname_invalid(SYM_REF, "refs/heads/master.lock"); + +/* NoDirectorySuffix */ + ensure_refname_invalid(SYM_REF, "refs/heads/master/"); + +/* NoSpace */ + ensure_refname_invalid(SYM_REF, "refs/heads/i haz space"); + +/* NoAsciiControlCharacters */ + { + char c; + char buffer[GIT_REFNAME_MAX]; + for (c = '\1'; c < ' '; c++) { + strncpy(buffer, "refs/heads/mast", 15); + strncpy(buffer + 15, (const char *)&c, 1); + strncpy(buffer + 16, "er", 2); + buffer[18 - 1] = '\0'; + ensure_refname_invalid(SYM_REF, buffer); + } + } + +/* NoBareDot */ + ensure_refname_invalid(SYM_REF, "refs/heads/."); + ensure_refname_invalid(SYM_REF, "refs/heads/.."); + ensure_refname_invalid(SYM_REF, "refs/heads/./master"); + ensure_refname_invalid(SYM_REF, "refs/heads/../master"); + +/* NoLeadingOrTrailingDot */ + ensure_refname_invalid(SYM_REF, "."); + ensure_refname_invalid(SYM_REF, "refs/heads/.bar"); + ensure_refname_invalid(SYM_REF, "refs/heads/..bar"); + ensure_refname_invalid(SYM_REF, "refs/heads/bar."); + +/* ContainsDot */ + ensure_refname_normalized(SYM_REF, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); + ensure_refname_invalid(SYM_REF, "refs/heads/master..pu"); + +/* NoMagicRefCharacters */ + ensure_refname_invalid(SYM_REF, "refs/heads/master^"); + ensure_refname_invalid(SYM_REF, "refs/heads/^master"); + ensure_refname_invalid(SYM_REF, "^refs/heads/master"); + + ensure_refname_invalid(SYM_REF, "refs/heads/master~"); + ensure_refname_invalid(SYM_REF, "refs/heads/~master"); + ensure_refname_invalid(SYM_REF, "~refs/heads/master"); + + ensure_refname_invalid(SYM_REF, "refs/heads/master:"); + ensure_refname_invalid(SYM_REF, "refs/heads/:master"); + ensure_refname_invalid(SYM_REF, ":refs/heads/master"); + +/* ShellGlob */ + ensure_refname_invalid(SYM_REF, "refs/heads/master?"); + ensure_refname_invalid(SYM_REF, "refs/heads/?master"); + ensure_refname_invalid(SYM_REF, "?refs/heads/master"); + + ensure_refname_invalid(SYM_REF, "refs/heads/master["); + ensure_refname_invalid(SYM_REF, "refs/heads/[master"); + ensure_refname_invalid(SYM_REF, "[refs/heads/master"); + + ensure_refname_invalid(SYM_REF, "refs/heads/master*"); + ensure_refname_invalid(SYM_REF, "refs/heads/*master"); + ensure_refname_invalid(SYM_REF, "*refs/heads/master"); + +/* ValidSpecialCharacters */ + ensure_refname_normalized(SYM_REF, "refs/heads/!", "refs/heads/!"); + ensure_refname_normalized(SYM_REF, "refs/heads/\"", "refs/heads/\""); + ensure_refname_normalized(SYM_REF, "refs/heads/#", "refs/heads/#"); + ensure_refname_normalized(SYM_REF, "refs/heads/$", "refs/heads/$"); + ensure_refname_normalized(SYM_REF, "refs/heads/%", "refs/heads/%"); + ensure_refname_normalized(SYM_REF, "refs/heads/&", "refs/heads/&"); + ensure_refname_normalized(SYM_REF, "refs/heads/'", "refs/heads/'"); + ensure_refname_normalized(SYM_REF, "refs/heads/(", "refs/heads/("); + ensure_refname_normalized(SYM_REF, "refs/heads/)", "refs/heads/)"); + ensure_refname_normalized(SYM_REF, "refs/heads/+", "refs/heads/+"); + ensure_refname_normalized(SYM_REF, "refs/heads/,", "refs/heads/,"); + ensure_refname_normalized(SYM_REF, "refs/heads/-", "refs/heads/-"); + ensure_refname_normalized(SYM_REF, "refs/heads/;", "refs/heads/;"); + ensure_refname_normalized(SYM_REF, "refs/heads/<", "refs/heads/<"); + ensure_refname_normalized(SYM_REF, "refs/heads/=", "refs/heads/="); + ensure_refname_normalized(SYM_REF, "refs/heads/>", "refs/heads/>"); + ensure_refname_normalized(SYM_REF, "refs/heads/@", "refs/heads/@"); + ensure_refname_normalized(SYM_REF, "refs/heads/]", "refs/heads/]"); + ensure_refname_normalized(SYM_REF, "refs/heads/_", "refs/heads/_"); + ensure_refname_normalized(SYM_REF, "refs/heads/`", "refs/heads/`"); + ensure_refname_normalized(SYM_REF, "refs/heads/{", "refs/heads/{"); + ensure_refname_normalized(SYM_REF, "refs/heads/|", "refs/heads/|"); + ensure_refname_normalized(SYM_REF, "refs/heads/}", "refs/heads/}"); + + // This is valid on UNIX, but not on Windows + // hence we make in invalid due to non-portability + // + ensure_refname_invalid(SYM_REF, "refs/heads/\\"); + +/* UnicodeNames */ + /* + * Currently this fails. + * ensure_refname_normalized(SYM_REF, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"); + */ + +/* RefLogQueryIsValidRef */ + ensure_refname_invalid(SYM_REF, "refs/heads/master@{1}"); + ensure_refname_invalid(SYM_REF, "refs/heads/master@{1.hour.ago}"); +} diff --git a/tests-clar/refs/overwrite.c b/tests-clar/refs/overwrite.c new file mode 100644 index 000000000..410e39a84 --- /dev/null +++ b/tests-clar/refs/overwrite.c @@ -0,0 +1,136 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *ref_name = "refs/heads/other"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_branch_name = "refs/heads/branch"; +static const char *ref_test_name = "refs/heads/test"; + +static git_repository *g_repo; + +void test_refs_overwrite__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_overwrite__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_overwrite__symbolic(void) +{ + // Overwrite an existing symbolic reference + git_reference *ref, *branch_ref; + + /* The target needds to exist and we need to check the name has changed */ + cl_git_pass(git_reference_create_symbolic(&branch_ref, g_repo, ref_branch_name, ref_master_name, 0)); + cl_git_pass(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_branch_name, 0)); + git_reference_free(ref); + + /* Ensure it points to the right place*/ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC); + cl_assert_equal_s(git_reference_target(ref), ref_branch_name); + git_reference_free(ref); + + /* Ensure we can't create it unless we force it to */ + cl_git_fail(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_master_name, 0)); + cl_git_pass(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_master_name, 1)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC); + cl_assert_equal_s(git_reference_target(ref), ref_master_name); + + git_reference_free(ref); + git_reference_free(branch_ref); +} + +void test_refs_overwrite__object_id(void) +{ + // Overwrite an existing object id reference + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + git_oid_cpy(&id, git_reference_oid(ref)); + git_reference_free(ref); + + /* Create it */ + cl_git_pass(git_reference_create_oid(&ref, g_repo, ref_name, &id, 0)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + git_oid_cpy(&id, git_reference_oid(ref)); + git_reference_free(ref); + + /* Ensure we can't overwrite unless we force it */ + cl_git_fail(git_reference_create_oid(&ref, g_repo, ref_name, &id, 0)); + cl_git_pass(git_reference_create_oid(&ref, g_repo, ref_name, &id, 1)); + git_reference_free(ref); + + /* Ensure it has been overwritten */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(!git_oid_cmp(&id, git_reference_oid(ref))); + + git_reference_free(ref); +} + +void test_refs_overwrite__object_id_with_symbolic(void) +{ + // Overwrite an existing object id reference with a symbolic one + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + git_oid_cpy(&id, git_reference_oid(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_create_oid(&ref, g_repo, ref_name, &id, 0)); + git_reference_free(ref); + cl_git_fail(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_master_name, 0)); + cl_git_pass(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_master_name, 1)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC); + cl_assert_equal_s(git_reference_target(ref), ref_master_name); + + git_reference_free(ref); +} + +void test_refs_overwrite__symbolic_with_object_id(void) +{ + // Overwrite an existing symbolic reference with an object id one + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + git_oid_cpy(&id, git_reference_oid(ref)); + git_reference_free(ref); + + /* Create the symbolic ref */ + cl_git_pass(git_reference_create_symbolic(&ref, g_repo, ref_name, ref_master_name, 0)); + git_reference_free(ref); + /* It shouldn't overwrite unless we tell it to */ + cl_git_fail(git_reference_create_oid(&ref, g_repo, ref_name, &id, 0)); + cl_git_pass(git_reference_create_oid(&ref, g_repo, ref_name, &id, 1)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + cl_assert(!git_oid_cmp(git_reference_oid(ref), &id)); + + git_reference_free(ref); +} diff --git a/tests-clar/refs/pack.c b/tests-clar/refs/pack.c new file mode 100644 index 000000000..305594c28 --- /dev/null +++ b/tests-clar/refs/pack.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; + +static git_repository *g_repo; + +void test_refs_pack__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_pack__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_pack__empty(void) +{ + // create a packfile for an empty folder + git_buf temp_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_join_n(&temp_path, '/', 3, g_repo->path_repository, GIT_REFS_HEADS_DIR, "empty_dir")); + cl_git_pass(git_futils_mkdir_r(temp_path.ptr, NULL, GIT_REFS_DIR_MODE)); + git_buf_free(&temp_path); + + cl_git_pass(git_reference_packall(g_repo)); +} + +void test_refs_pack__loose(void) +{ + // create a packfile from all the loose rn a repo + git_reference *reference; + git_buf temp_path = GIT_BUF_INIT; + + /* Ensure a known loose ref can be looked up */ + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(git_reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + git_reference_free(reference); + + /* + * We are now trying to pack also a loose reference + * called `points_to_blob`, to make sure we can properly + * pack weak tags + */ + cl_git_pass(git_reference_packall(g_repo)); + + /* Ensure the packed-refs file exists */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, GIT_PACKEDREFS_FILE)); + cl_assert(git_path_exists(temp_path.ptr)); + + /* Ensure the known ref can still be looked up but is now packed */ + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(git_reference_is_packed(reference)); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + + /* Ensure the known ref has been removed from the loose folder structure */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, loose_tag_ref_name)); + cl_assert(!git_path_exists(temp_path.ptr)); + + git_reference_free(reference); + git_buf_free(&temp_path); +} diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c new file mode 100644 index 000000000..d7111b232 --- /dev/null +++ b/tests-clar/refs/read.c @@ -0,0 +1,194 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; +static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; +static const char *head_tracker_sym_ref_name = "head-tracker"; +static const char *current_head_target = "refs/heads/master"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *packed_head_name = "refs/heads/packed"; +static const char *packed_test_head_name = "refs/heads/packed-test"; + +static git_repository *g_repo; + +void test_refs_read__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_read__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_read__loose_tag(void) +{ + // lookup a loose tag reference + git_reference *reference; + git_object *object; + git_buf ref_name_from_tag_name = GIT_BUF_INIT; + + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(git_reference_type(reference) & GIT_REF_OID); + cl_assert(git_reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_oid(reference), GIT_OBJ_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJ_TAG); + + /* Ensure the name of the tag matches the name of the reference */ + cl_git_pass(git_buf_joinpath(&ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object))); + cl_assert_equal_s(ref_name_from_tag_name.ptr, loose_tag_ref_name); + git_buf_free(&ref_name_from_tag_name); + + git_object_free(object); + + git_reference_free(reference); +} + +void test_refs_read__nonexisting_tag(void) +{ + // lookup a loose tag reference that doesn't exist + git_reference *reference; + + cl_git_fail(git_reference_lookup(&reference, g_repo, non_existing_tag_ref_name)); + + git_reference_free(reference); +} + + +void test_refs_read__symbolic(void) +{ + // lookup a symbolic reference + git_reference *reference, *resolved_ref; + git_object *object; + git_oid id; + + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC); + cl_assert(git_reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, GIT_HEAD_FILE); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJ_COMMIT); + + git_oid_fromstr(&id, current_master_tip); + cl_assert(git_oid_cmp(&id, git_object_id(object)) == 0); + + git_object_free(object); + + git_reference_free(reference); + git_reference_free(resolved_ref); +} + +void test_refs_read__nested_symbolic(void) +{ + // lookup a nested symbolic reference + git_reference *reference, *resolved_ref; + git_object *object; + git_oid id; + + cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); + cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC); + cl_assert(git_reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, head_tracker_sym_ref_name); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJ_COMMIT); + + git_oid_fromstr(&id, current_master_tip); + cl_assert(git_oid_cmp(&id, git_object_id(object)) == 0); + + git_object_free(object); + + git_reference_free(reference); + git_reference_free(resolved_ref); +} + +void test_refs_read__head_then_master(void) +{ + // lookup the HEAD and resolve the master branch + git_reference *reference, *resolved_ref, *comp_base_ref; + + cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); + cl_git_pass(git_reference_resolve(&comp_base_ref, reference)); + git_reference_free(reference); + + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_git_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); + git_reference_free(reference); + git_reference_free(resolved_ref); + + cl_git_pass(git_reference_lookup(&reference, g_repo, current_head_target)); + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_git_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); + git_reference_free(reference); + git_reference_free(resolved_ref); + + git_reference_free(comp_base_ref); +} + +void test_refs_read__master_then_head(void) +{ + // lookup the master branch and then the HEAD + git_reference *reference, *master_ref, *resolved_ref; + + cl_git_pass(git_reference_lookup(&master_ref, g_repo, current_head_target)); + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_git_pass(git_oid_cmp(git_reference_oid(master_ref), git_reference_oid(resolved_ref))); + + git_reference_free(reference); + git_reference_free(resolved_ref); + git_reference_free(master_ref); +} + + +void test_refs_read__packed(void) +{ + // lookup a packed reference + git_reference *reference; + git_object *object; + + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); + cl_assert(git_reference_type(reference) & GIT_REF_OID); + cl_assert(git_reference_is_packed(reference)); + cl_assert_equal_s(reference->name, packed_head_name); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_oid(reference), GIT_OBJ_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJ_COMMIT); + + git_object_free(object); + + git_reference_free(reference); +} + +void test_refs_read__loose_first(void) +{ + // assure that a loose reference is looked up before a packed reference + git_reference *reference; + + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); + git_reference_free(reference); + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_test_head_name)); + cl_assert(git_reference_type(reference) & GIT_REF_OID); + cl_assert(git_reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, packed_test_head_name); + + git_reference_free(reference); +} diff --git a/tests-clar/refs/reflog.c b/tests-clar/refs/reflog.c new file mode 100644 index 000000000..1bc51b2b8 --- /dev/null +++ b/tests-clar/refs/reflog.c @@ -0,0 +1,123 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + + +static const char *new_ref = "refs/heads/test-reflog"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *commit_msg = "commit: bla bla"; + +static git_repository *g_repo; + + +// helpers +static void assert_signature(git_signature *expected, git_signature *actual) +{ + cl_assert(actual); + cl_assert_equal_s(expected->name, actual->name); + cl_assert_equal_s(expected->email, actual->email); + cl_assert(expected->when.offset == actual->when.offset); + cl_assert(expected->when.time == actual->when.time); +} + + +// Fixture setup and teardown +void test_refs_reflog__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_reflog__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_reflog__write_then_read(void) +{ + // 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_reflog *reflog; + git_reflog_entry *entry; + char oid_str[GIT_OID_HEXSZ+1]; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); + cl_git_fail(git_reflog_write(ref, NULL, committer, "no ancestor NULL for an existing reflog")); + cl_git_fail(git_reflog_write(ref, NULL, committer, "no\nnewline")); + cl_git_pass(git_reflog_write(ref, &oid, committer, commit_msg)); + + /* Reopen a new instance of the repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + /* Lookup the preivously created branch */ + 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_assert(reflog->entries.length == 2); + + entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0); + assert_signature(committer, entry->committer); + git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); + cl_assert_equal_s("0000000000000000000000000000000000000000", oid_str); + git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); + cl_assert_equal_s(current_master_tip, oid_str); + cl_assert(entry->msg == NULL); + + entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 1); + assert_signature(committer, entry->committer); + git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); + cl_assert_equal_s(current_master_tip, oid_str); + git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); + cl_assert_equal_s(current_master_tip, oid_str); + 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__dont_write_bad(void) +{ + // avoid writing an obviously wrong reflog + 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_oid(&ref, g_repo, new_ref, &oid, 0)); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + /* Write the reflog for the new branch */ + cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); + + /* Try to update the reflog with wrong information: + * It's no new reference, so the ancestor OID cannot + * be NULL. */ + cl_git_fail(git_reflog_write(ref, NULL, committer, NULL)); + + git_signature_free(committer); + + git_reference_free(ref); +} diff --git a/tests-clar/refs/rename.c b/tests-clar/refs/rename.c new file mode 100644 index 000000000..4b917ef6d --- /dev/null +++ b/tests-clar/refs/rename.c @@ -0,0 +1,339 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; +static const char *packed_head_name = "refs/heads/packed"; +static const char *packed_test_head_name = "refs/heads/packed-test"; +static const char *ref_one_name = "refs/heads/one/branch"; +static const char *ref_one_name_new = "refs/heads/two/branch"; +static const char *ref_two_name = "refs/heads/two"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_two_name_new = "refs/heads/two/two"; + +static git_repository *g_repo; + + + +void test_refs_rename__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_rename__loose(void) +{ + // rename a loose reference + git_reference *looked_up_ref, *another_looked_up_ref; + git_buf temp_path = GIT_BUF_INIT; + const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu"; + + /* Ensure the ref doesn't exist on the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, new_name)); + cl_assert(!git_path_exists(temp_path.ptr)); + + /* Retrieval of the reference to rename */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, loose_tag_ref_name)); + + /* ... which is indeed loose */ + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(looked_up_ref, new_name, 0)); + cl_assert_equal_s(looked_up_ref->name, new_name); + + /* ...It can't be looked-up with the old name... */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, loose_tag_ref_name)); + + /* ...but the new name works ok... */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, new_name)); + cl_assert_equal_s(another_looked_up_ref->name, new_name); + + /* .. the ref is still loose... */ + cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + + /* ...and the ref can be found in the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, new_name)); + cl_assert(git_path_exists(temp_path.ptr)); + + git_reference_free(looked_up_ref); + git_reference_free(another_looked_up_ref); + git_buf_free(&temp_path); +} + +void test_refs_rename__packed(void) +{ + // rename a packed reference (should make it loose) + git_reference *looked_up_ref, *another_looked_up_ref; + git_buf temp_path = GIT_BUF_INIT; + const char *brand_new_name = "refs/heads/brand_new_name"; + + /* Ensure the ref doesn't exist on the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, packed_head_name)); + cl_assert(!git_path_exists(temp_path.ptr)); + + /* The reference can however be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* .. and it's packed */ + cl_assert(git_reference_is_packed(looked_up_ref) != 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(looked_up_ref, brand_new_name, 0)); + cl_assert_equal_s(looked_up_ref->name, brand_new_name); + + /* ...It can't be looked-up with the old name... */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_head_name)); + + /* ...but the new name works ok... */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, brand_new_name)); + cl_assert_equal_s(another_looked_up_ref->name, brand_new_name); + + /* .. the ref is no longer packed... */ + cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + cl_assert(git_reference_is_packed(looked_up_ref) == 0); + + /* ...and the ref now happily lives in the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, brand_new_name)); + cl_assert(git_path_exists(temp_path.ptr)); + + git_reference_free(looked_up_ref); + git_reference_free(another_looked_up_ref); + git_buf_free(&temp_path); +} + +void test_refs_rename__packed_doesnt_pack_others(void) +{ + // renaming a packed reference does not pack another reference which happens to be in both loose and pack state + git_reference *looked_up_ref, *another_looked_up_ref; + git_buf temp_path = GIT_BUF_INIT; + const char *brand_new_name = "refs/heads/brand_new_name"; + + /* Ensure the other reference exists on the file system */ + cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, packed_test_head_name)); + cl_assert(git_path_exists(temp_path.ptr)); + + /* Lookup the other reference */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's loose */ + cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + git_reference_free(another_looked_up_ref); + + /* Lookup the reference to rename */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* Ensure it's packed */ + cl_assert(git_reference_is_packed(looked_up_ref) != 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(looked_up_ref, brand_new_name, 0)); + + /* Lookup the other reference */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's loose */ + cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + + /* Ensure the other ref still exists on the file system */ + cl_assert(git_path_exists(temp_path.ptr)); + + git_reference_free(looked_up_ref); + git_reference_free(another_looked_up_ref); + git_buf_free(&temp_path); +} + +void test_refs_rename__name_collision(void) +{ + // can not rename a reference with the name of an existing reference + git_reference *looked_up_ref; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* Can not be renamed to the name of another existing reference. */ + cl_git_fail(git_reference_rename(looked_up_ref, packed_test_head_name, 0)); + git_reference_free(looked_up_ref); + + /* Failure to rename it hasn't corrupted its state */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_head_name); + + git_reference_free(looked_up_ref); +} + +void test_refs_rename__invalid_name(void) +{ + // can not rename a reference with an invalid name + git_reference *looked_up_ref; + + /* An existing oid reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + + /* Can not be renamed with an invalid name. */ + cl_git_fail(git_reference_rename(looked_up_ref, "Hello! I'm a very invalid name.", 0)); + + /* Can not be renamed outside of the refs hierarchy. */ + cl_git_fail(git_reference_rename(looked_up_ref, "i-will-sudo-you", 0)); + + /* Failure to rename it hasn't corrupted its state */ + git_reference_free(looked_up_ref); + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); + + git_reference_free(looked_up_ref); +} + +void test_refs_rename__force_loose_packed(void) +{ + // can force-rename a packed reference with the name of an existing loose and packed reference + git_reference *looked_up_ref; + git_oid oid; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + git_oid_cpy(&oid, git_reference_oid(looked_up_ref)); + + /* Can be force-renamed to the name of another existing reference. */ + cl_git_pass(git_reference_rename(looked_up_ref, packed_test_head_name, 1)); + git_reference_free(looked_up_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); + cl_assert(!git_oid_cmp(&oid, git_reference_oid(looked_up_ref))); + git_reference_free(looked_up_ref); + + /* And that the previous one doesn't exist any longer */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); +} + +void test_refs_rename__force_loose(void) +{ + // can force-rename a loose reference with the name of an existing loose reference + git_reference *looked_up_ref; + git_oid oid; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); + git_oid_cpy(&oid, git_reference_oid(looked_up_ref)); + + /* Can be force-renamed to the name of another existing reference. */ + cl_git_pass(git_reference_rename(looked_up_ref, "refs/heads/test", 1)); + git_reference_free(looked_up_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/test")); + cl_assert_equal_s(looked_up_ref->name, "refs/heads/test"); + cl_assert(!git_oid_cmp(&oid, git_reference_oid(looked_up_ref))); + git_reference_free(looked_up_ref); + + /* And that the previous one doesn't exist any longer */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); + + git_reference_free(looked_up_ref); +} + + +void test_refs_rename__overwrite(void) +{ + // can not overwrite name of existing reference + git_reference *ref, *ref_one, *ref_one_new, *ref_two; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + + git_oid_cpy(&id, git_reference_oid(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create_oid(&ref_one, g_repo, ref_one_name, &id, 0)); + cl_git_pass(git_reference_create_oid(&ref_two, g_repo, ref_two_name, &id, 0)); + + /* Pack everything */ + cl_git_pass(git_reference_packall(g_repo)); + + /* Attempt to create illegal reference */ + cl_git_fail(git_reference_create_oid(&ref_one_new, g_repo, ref_one_name_new, &id, 0)); + + /* Illegal reference couldn't be created so this is supposed to fail */ + cl_git_fail(git_reference_lookup(&ref_one_new, g_repo, ref_one_name_new)); + + git_reference_free(ref); + git_reference_free(ref_one); + git_reference_free(ref_one_new); + git_reference_free(ref_two); +} + + +void test_refs_rename__prefix(void) +{ + // can be renamed to a new name prefixed with the old name + git_reference *ref, *ref_two, *looked_up_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + + git_oid_cpy(&id, git_reference_oid(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create_oid(&ref_two, g_repo, ref_two_name, &id, 0)); + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + + /* Can be rename to a new name starting with the old name. */ + cl_git_pass(git_reference_rename(looked_up_ref, ref_two_name_new, 0)); + git_reference_free(looked_up_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + cl_assert_equal_s(looked_up_ref->name, ref_two_name_new); + git_reference_free(looked_up_ref); + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + + git_reference_free(ref); + git_reference_free(ref_two); + git_reference_free(looked_up_ref); +} + +void test_refs_rename__move_up(void) +{ + // can move a reference to a upper reference hierarchy + git_reference *ref, *ref_two, *looked_up_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REF_OID); + + git_oid_cpy(&id, git_reference_oid(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create_oid(&ref_two, g_repo, ref_two_name_new, &id, 0)); + git_reference_free(ref_two); + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + + /* Can be renamed upward the reference tree. */ + cl_git_pass(git_reference_rename(looked_up_ref, ref_two_name, 0)); + git_reference_free(looked_up_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + cl_assert_equal_s(looked_up_ref->name, ref_two_name); + git_reference_free(looked_up_ref); + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + git_reference_free(ref); + git_reference_free(looked_up_ref); +} diff --git a/tests-clar/refs/unicode.c b/tests-clar/refs/unicode.c new file mode 100644 index 000000000..889c85666 --- /dev/null +++ b/tests-clar/refs/unicode.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" + +static git_repository *repo; + +void test_refs_unicode__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + + cl_git_pass(git_repository_open(&repo, "testrepo.git")); +} + +void test_refs_unicode__cleanup(void) +{ + git_repository_free(repo); + cl_fixture_cleanup("testrepo.git"); +} + +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 *master = "refs/heads/master"; + + /* Create the reference */ + cl_git_pass(git_reference_lookup(&ref0, repo, master)); + cl_git_pass(git_reference_create_oid(&ref1, repo, REFNAME, git_reference_oid(ref0), 0)); + cl_assert(strcmp(REFNAME, git_reference_name(ref1)) == 0); + + /* 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_oid(ref1), git_reference_oid(ref2)) == 0); + cl_assert(strcmp(REFNAME, git_reference_name(ref2)) == 0); + + git_reference_free(ref0); + git_reference_free(ref1); + git_reference_free(ref2); + git_repository_free(repo2); +} diff --git a/tests-clar/repo/discover.c b/tests-clar/repo/discover.c new file mode 100644 index 000000000..b3d639bd1 --- /dev/null +++ b/tests-clar/repo/discover.c @@ -0,0 +1,142 @@ +#include "clar_libgit2.h" + +#include "odb.h" +#include "repository.h" + + +#define TEMP_REPO_FOLDER "temprepo/" +#define DISCOVER_FOLDER TEMP_REPO_FOLDER "discover.git" + +#define SUB_REPOSITORY_FOLDER_NAME "sub_repo" +#define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME +#define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub" +#define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub" +#define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub" + +#define REPOSITORY_ALTERNATE_FOLDER DISCOVER_FOLDER "/alternate_sub_repo" +#define REPOSITORY_ALTERNATE_FOLDER_SUB REPOSITORY_ALTERNATE_FOLDER "/sub" +#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB "/subsub" +#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/subsubsub" + +#define ALTERNATE_MALFORMED_FOLDER1 DISCOVER_FOLDER "/alternate_malformed_repo1" +#define ALTERNATE_MALFORMED_FOLDER2 DISCOVER_FOLDER "/alternate_malformed_repo2" +#define ALTERNATE_MALFORMED_FOLDER3 DISCOVER_FOLDER "/alternate_malformed_repo3" +#define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo" + +static void ensure_repository_discover(const char *start_path, + const char *ceiling_dirs, + const char *expected_path) +{ + char found_path[GIT_PATH_MAX]; + cl_git_pass(git_repository_discover(found_path, sizeof(found_path), start_path, 0, ceiling_dirs)); + //across_fs is always 0 as we can't automate the filesystem change tests + cl_assert_equal_s(found_path, expected_path); +} + +static void write_file(const char *path, const char *content) +{ + git_file file; + int error; + + if (git_path_exists(path)) { + cl_git_pass(p_unlink(path)); + } + + file = git_futils_creat_withpath(path, 0777, 0666); + cl_assert(file >= 0); + + error = p_write(file, content, strlen(content) * sizeof(char)); + p_close(file); + cl_git_pass(error); +} + +//no check is performed on ceiling_dirs length, so be sure it's long enough +static void append_ceiling_dir(git_buf *ceiling_dirs, const char *path) +{ + git_buf pretty_path = GIT_BUF_INIT; + char ceiling_separator[2] = { GIT_PATH_LIST_SEPARATOR, '\0' }; + + cl_git_pass(git_path_prettify_dir(&pretty_path, path, NULL)); + + if (ceiling_dirs->size > 0) + git_buf_puts(ceiling_dirs, ceiling_separator); + + git_buf_puts(ceiling_dirs, pretty_path.ptr); + + git_buf_free(&pretty_path); + cl_assert(git_buf_oom(ceiling_dirs) == 0); +} + +void test_repo_discover__0(void) +{ + // test discover + git_repository *repo; + git_buf ceiling_dirs_buf = GIT_BUF_INIT; + const char *ceiling_dirs; + char repository_path[GIT_PATH_MAX]; + char sub_repository_path[GIT_PATH_MAX]; + char found_path[GIT_PATH_MAX]; + const mode_t mode = 0777; + + git_futils_mkdir_r(DISCOVER_FOLDER, NULL, mode); + append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER); + ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(repository_path, sizeof(repository_path), DISCOVER_FOLDER, 0, ceiling_dirs)); + + cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1)); + cl_git_pass(git_repository_discover(repository_path, sizeof(repository_path), DISCOVER_FOLDER, 0, ceiling_dirs)); + git_repository_free(repo); + + cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0)); + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode)); + cl_git_pass(git_repository_discover(sub_repository_path, sizeof(sub_repository_path), SUB_REPOSITORY_FOLDER, 0, ceiling_dirs)); + + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode)); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, sub_repository_path); + + cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, NULL, mode)); + write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); + write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); + write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path); + + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, NULL, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, NULL, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, NULL, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, NULL, mode)); + write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"); + cl_git_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs)); + cl_git_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs)); + cl_git_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(found_path, sizeof(found_path), ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs)); + + append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER); + ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); + + //this must pass as ceiling_directories cannot predent the current + //working directory to be checked + cl_git_pass(git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER, 0, ceiling_dirs)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs)); + + //.gitfile redirection should not be affected by ceiling directories + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path); + + cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, GIT_DIRREMOVAL_FILES_AND_DIRS)); + git_repository_free(repo); + git_buf_free(&ceiling_dirs_buf); +} + diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c new file mode 100644 index 000000000..966de1f16 --- /dev/null +++ b/tests-clar/repo/getters.c @@ -0,0 +1,86 @@ +#include "clar_libgit2.h" + +void test_repo_getters__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); +} + +void test_repo_getters__cleanup(void) +{ + cl_fixture_cleanup("testrepo.git"); +} + +void test_repo_getters__empty(void) +{ + git_repository *repo_empty, *repo_normal; + + cl_git_pass(git_repository_open(&repo_normal, cl_fixture("testrepo.git"))); + cl_assert(git_repository_is_empty(repo_normal) == 0); + git_repository_free(repo_normal); + + cl_git_pass(git_repository_open(&repo_empty, cl_fixture("empty_bare.git"))); + cl_assert(git_repository_is_empty(repo_empty) == 1); + git_repository_free(repo_empty); +} + +void test_repo_getters__head_detached(void) +{ + git_repository *repo; + git_reference *ref; + git_oid oid; + + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_assert(git_repository_head_detached(repo) == 0); + + /* detach the HEAD */ + git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); + cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); + cl_assert(git_repository_head_detached(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_detached(repo) == 0); + + git_reference_free(ref); + git_repository_free(repo); +} + +void test_repo_getters__head_orphan(void) +{ + git_repository *repo; + git_reference *ref; + + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_assert(git_repository_head_orphan(repo) == 0); + + /* orphan HEAD */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); + cl_assert(git_repository_head_orphan(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_orphan(repo) == 0); + + git_reference_free(ref); + git_repository_free(repo); +} + +void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) +{ + git_odb *odb; + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_assert(((git_refcount *)odb)->refcount == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)odb)->refcount == 1); + + git_odb_free(odb); +} diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c new file mode 100644 index 000000000..af54b2266 --- /dev/null +++ b/tests-clar/repo/init.c @@ -0,0 +1,235 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "repository.h" +#include "config.h" + +enum repo_mode { + STANDARD_REPOSITORY = 0, + BARE_REPOSITORY = 1 +}; + +static git_repository *_repo; + +void test_repo_init__initialize(void) +{ + _repo = NULL; +} + +static void cleanup_repository(void *path) +{ + git_repository_free(_repo); + cl_fixture_cleanup((const char *)path); +} + +static void ensure_repository_init( + const char *working_directory, + int is_bare, + const char *expected_path_repository, + const char *expected_working_directory) +{ + const char *workdir; + + cl_git_pass(git_repository_init(&_repo, working_directory, is_bare)); + + workdir = git_repository_workdir(_repo); + if (workdir != NULL || expected_working_directory != NULL) { + cl_assert( + git__suffixcmp(workdir, expected_working_directory) == 0 + ); + } + + cl_assert( + git__suffixcmp(git_repository_path(_repo), expected_path_repository) == 0 + ); + + cl_assert(git_repository_is_bare(_repo) == is_bare); + +#ifdef GIT_WIN32 + if (!is_bare) { + cl_assert((GetFileAttributes(git_repository_path(_repo)) & FILE_ATTRIBUTE_HIDDEN) != 0); + } +#endif + + cl_assert(git_repository_is_empty(_repo)); +} + +void test_repo_init__standard_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo"); + ensure_repository_init("testrepo/", 0, "testrepo/.git/", "testrepo/"); +} + +void test_repo_init__standard_repo_noslash(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo"); + ensure_repository_init("testrepo", 0, "testrepo/.git/", "testrepo/"); +} + +void test_repo_init__bare_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo.git"); + ensure_repository_init("testrepo.git/", 1, "testrepo.git/", NULL); +} + +void test_repo_init__bare_repo_noslash(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo.git"); + ensure_repository_init("testrepo.git", 1, "testrepo.git/", NULL); +} + +void test_repo_init__bare_repo_escaping_current_workdir(void) +{ + git_buf path_repository = GIT_BUF_INIT; + git_buf path_current_workdir = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL)); + + cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c")); + cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE)); + + /* Change the current working directory */ + cl_git_pass(chdir(git_buf_cstr(&path_repository))); + + /* Initialize a bare repo with a relative path escaping out of the current working directory */ + cl_git_pass(git_repository_init(&_repo, "../d/e.git", 1)); + cl_git_pass(git__suffixcmp(git_repository_path(_repo), "/a/b/d/e.git/")); + + git_repository_free(_repo); + + /* Open a bare repo with a relative path escaping out of the current working directory */ + cl_git_pass(git_repository_open(&_repo, "../d/e.git")); + + cl_git_pass(chdir(git_buf_cstr(&path_current_workdir))); + + git_buf_free(&path_current_workdir); + git_buf_free(&path_repository); + + cleanup_repository("a"); +} + +void test_repo_init__reinit_bare_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "reinit.git"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&_repo, "reinit.git", 1)); + git_repository_free(_repo); + + /* Reinitialize the repository */ + cl_git_pass(git_repository_init(&_repo, "reinit.git", 1)); +} + +void test_repo_init__reinit_too_recent_bare_repo(void) +{ + git_config *config; + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&_repo, "reinit.git", 1)); + git_repository_config(&config, _repo); + + /* + * Hack the config of the repository to make it look like it has + * been created by a recenter version of git/libgit2 + */ + cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 42)); + + git_config_free(config); + git_repository_free(_repo); + + /* Try to reinitialize the repository */ + cl_git_fail(git_repository_init(&_repo, "reinit.git", 1)); + + cl_fixture_cleanup("reinit.git"); +} + +void test_repo_init__additional_templates(void) +{ + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(&cleanup_repository, "tester"); + + ensure_repository_init("tester", 0, "tester/.git/", "tester/"); + + cl_git_pass( + git_buf_joinpath(&path, git_repository_path(_repo), "description")); + cl_assert(git_path_isfile(git_buf_cstr(&path))); + + cl_git_pass( + git_buf_joinpath(&path, git_repository_path(_repo), "info/exclude")); + cl_assert(git_path_isfile(git_buf_cstr(&path))); + + cl_git_pass( + git_buf_joinpath(&path, git_repository_path(_repo), "hooks")); + cl_assert(git_path_isdir(git_buf_cstr(&path))); + /* won't confirm specific contents of hooks dir since it may vary */ + + git_buf_free(&path); +} + +static void assert_config_entry_on_init(const char *config_key, int expected_value) +{ + git_config *config; + int current_value; + + cl_set_cleanup(&cleanup_repository, "config_entry"); + + cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", 1)); + git_repository_config(&config, _repo); + + if (expected_value >= 0) { + cl_git_pass(git_config_get_bool(¤t_value, config, config_key)); + + 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); +} + +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 +} + +#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); +#else + assert_config_entry_on_init("core.ignorecase", GIT_ENOTFOUND); +#endif +} + +void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) +{ + git_config *config; + int current_value; + + /* Init a new repo */ + test_repo_init__detect_ignorecase(); + + /* Change the "core.ignorecase" config value to something unlikely */ + git_repository_config(&config, _repo); + git_config_set_int32(config, "core.ignorecase", 42); + git_config_free(config); + git_repository_free(_repo); + + /* Reinit the repository */ + cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", 1)); + git_repository_config(&config, _repo); + + /* Ensure the "core.ignorecase" config value hasn't been updated */ + cl_git_pass(git_config_get_int32(¤t_value, config, "core.ignorecase")); + cl_assert_equal_i(42, current_value); + + git_config_free(config); +} diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c new file mode 100644 index 000000000..c70ec83a9 --- /dev/null +++ b/tests-clar/repo/open.c @@ -0,0 +1,282 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include + +void test_repo_open__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + if (git_path_isdir("alternate")) + git_futils_rmdir_r("alternate", GIT_DIRREMOVAL_FILES_AND_DIRS); +} + +void test_repo_open__bare_empty_repo(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_bare.git"); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + cl_assert(git_repository_workdir(repo) == NULL); +} + +void test_repo_open__standard_empty_repo_through_gitdir(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("empty_standard_repo/.gitted"))); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); + + git_repository_free(repo); +} + +void test_repo_open__standard_empty_repo_through_workdir(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); +} + + +void test_repo_open__open_with_discover(void) +{ + static const char *variants[] = { + "attr", "attr/", "attr/.git", "attr/.git/", + "attr/sub", "attr/sub/", "attr/sub/sub", "attr/sub/sub/", + NULL + }; + git_repository *repo; + const char **scan; + + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + for (scan = variants; *scan != NULL; scan++) { + cl_git_pass(git_repository_open_ext(&repo, *scan, 0, NULL)); + cl_assert(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0); + git_repository_free(repo); + } + + cl_fixture_cleanup("attr"); +} + +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"); + + cl_git_pass(git_repository_open(&repo2, "alternate")); + + cl_assert(git_repository_path(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_path(repo2), "empty_standard_repo/.git/") == 0, git_repository_path(repo2)); + cl_assert_equal_s(git_repository_path(repo), git_repository_path(repo2)); + + cl_assert(git_repository_workdir(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); + + git_repository_free(repo2); +} + +void test_repo_open__from_git_new_workdir(void) +{ + /* The git-new-workdir script that ships with git sets up a bunch of + * symlinks to create a second workdir that shares the object db with + * another checkout. Libgit2 can open a repo that has been configured + * this way. + */ + cl_git_sandbox_init("empty_standard_repo"); + +#ifndef GIT_WIN32 + git_repository *repo2; + git_buf link_tgt = GIT_BUF_INIT, link = GIT_BUF_INIT, body = GIT_BUF_INIT; + const char **scan; + int link_fd; + static const char *links[] = { + "config", "refs", "logs/refs", "objects", "info", "hooks", + "packed-refs", "remotes", "rr-cache", "svn", NULL + }; + static const char *copies[] = { + "HEAD", NULL + }; + + cl_git_pass(p_mkdir("alternate", 0777)); + cl_git_pass(p_mkdir("alternate/.git", 0777)); + + for (scan = links; *scan != NULL; scan++) { + git_buf_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); + if (git_path_exists(link_tgt.ptr)) { + git_buf_joinpath(&link_tgt, "../../empty_standard_repo/.git", *scan); + git_buf_joinpath(&link, "alternate/.git", *scan); + if (strchr(*scan, '/')) + git_futils_mkpath2file(link.ptr, 0777); + cl_assert_(symlink(link_tgt.ptr, link.ptr) == 0, strerror(errno)); + } + } + for (scan = copies; *scan != NULL; scan++) { + git_buf_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); + if (git_path_exists(link_tgt.ptr)) { + git_buf_joinpath(&link, "alternate/.git", *scan); + cl_git_pass(git_futils_readbuffer(&body, link_tgt.ptr)); + + cl_assert((link_fd = git_futils_creat_withpath(link.ptr, 0777, 0666)) >= 0); + cl_must_pass(p_write(link_fd, body.ptr, body.size)); + p_close(link_fd); + } + } + + git_buf_free(&link_tgt); + git_buf_free(&link); + git_buf_free(&body); + + + cl_git_pass(git_repository_open(&repo2, "alternate")); + + cl_assert(git_repository_path(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_path(repo2), "alternate/.git/") == 0, git_repository_path(repo2)); + + cl_assert(git_repository_workdir(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); + + git_repository_free(repo2); +#endif +} + +void test_repo_open__failures(void) +{ + git_repository *base, *repo; + git_buf ceiling = GIT_BUF_INIT; + + base = cl_git_sandbox_init("attr"); + cl_git_pass(git_buf_sets(&ceiling, git_repository_workdir(base))); + + /* fail with no searching */ + cl_git_fail(git_repository_open(&repo, "attr/sub")); + cl_git_fail(git_repository_open_ext( + &repo, "attr/sub", GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)); + + /* fail with ceiling too low */ + cl_git_pass(git_buf_joinpath(&ceiling, ceiling.ptr, "sub")); + cl_git_fail(git_repository_open_ext(&repo, "attr/sub", 0, ceiling.ptr)); + + /* fail with no repo */ + cl_git_pass(p_mkdir("alternate", 0777)); + cl_git_pass(p_mkdir("alternate/.git", 0777)); + cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); + cl_git_fail(git_repository_open_ext(&repo, "alternate/.git", 0, NULL)); + + git_buf_free(&ceiling); +} + +void test_repo_open__bad_gitlinks(void) +{ + git_repository *repo; + static const char *bad_links[] = { + "garbage\n", "gitdir", "gitdir:\n", "gitdir: foobar", + "gitdir: ../invalid", "gitdir: ../invalid2", + "gitdir: ../attr/.git with extra stuff", + NULL + }; + const char **scan; + + 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); + cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); + } + + git_futils_rmdir_r("invalid", GIT_DIRREMOVAL_FILES_AND_DIRS); + git_futils_rmdir_r("invalid2", GIT_DIRREMOVAL_FILES_AND_DIRS); +} + +#ifdef GIT_WIN32 +static void unposix_path(git_buf *path) +{ + char *src, *tgt; + + src = tgt = path->ptr; + + /* convert "/d/..." to "d:\..." */ + if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { + *tgt++ = src[1]; + *tgt++ = ':'; + *tgt++ = '\\'; + src += 3; + } + + while (*src) { + *tgt++ = (*src == '/') ? '\\' : *src; + src++; + } + + *tgt = '\0'; +} +#endif + +void test_repo_open__win32_path(void) +{ +#ifdef GIT_WIN32 + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"), *repo2; + git_buf winpath = GIT_BUF_INIT; + static const char *repo_path = "empty_standard_repo/.git/"; + static const char *repo_wd = "empty_standard_repo/"; + + cl_assert(git__suffixcmp(git_repository_path(repo), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), repo_wd) == 0); + + cl_git_pass(git_buf_sets(&winpath, git_repository_path(repo))); + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_buf_sets(&winpath, git_repository_path(repo))); + git_buf_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_buf_sets(&winpath, git_repository_workdir(repo))); + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_buf_sets(&winpath, git_repository_workdir(repo))); + git_buf_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + git_buf_free(&winpath); +#endif +} + +void test_repo_open__opening_a_non_existing_repository_returns_ENOTFOUND(void) +{ + git_repository *repo; + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_open(&repo, "i-do-not/exist")); +} diff --git a/tests-clar/repo/setters.c b/tests-clar/repo/setters.c new file mode 100644 index 000000000..6242d8541 --- /dev/null +++ b/tests-clar/repo/setters.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "util.h" + +static git_repository *repo; + +void test_repo_setters__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_must_pass(p_mkdir("new_workdir", 0777)); +} + +void test_repo_setters__cleanup(void) +{ + git_repository_free(repo); + cl_fixture_cleanup("testrepo.git"); + cl_must_pass(p_rmdir("new_workdir")); +} + +void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void) +{ + cl_assert(git_repository_is_bare(repo) == 1); + + cl_assert(git_repository_workdir(repo) == NULL); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git_repository_is_bare(repo) == 0); +} + +void test_repo_setters__setting_a_workdir_prettifies_its_path(void) +{ + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + + cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); +} + +void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void) +{ + git_index *new_index; + + cl_git_pass(git_index_open(&new_index, "./my-index")); + cl_assert(((git_refcount *)new_index)->refcount == 1); + + git_repository_set_index(repo, new_index); + cl_assert(((git_refcount *)new_index)->refcount == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)new_index)->refcount == 1); + + git_index_free(new_index); + + /* + * Ensure the cleanup method won't try to free the repo as it's already been taken care of + */ + repo = NULL; +} + +void test_repo_setters__setting_a_new_odb_on_a_repo_which_already_loaded_one_properly_honors_the_refcount(void) +{ + git_odb *new_odb; + + cl_git_pass(git_odb_open(&new_odb, "./testrepo.git/objects")); + cl_assert(((git_refcount *)new_odb)->refcount == 1); + + git_repository_set_odb(repo, new_odb); + cl_assert(((git_refcount *)new_odb)->refcount == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)new_odb)->refcount == 1); + + git_odb_free(new_odb); + + /* + * Ensure the cleanup method won't try to free the repo as it's already been taken care of + */ + repo = NULL; +} diff --git a/tests-clar/resources/.gitattributes b/tests-clar/resources/.gitattributes new file mode 100644 index 000000000..556f8c827 --- /dev/null +++ b/tests-clar/resources/.gitattributes @@ -0,0 +1 @@ +* binary diff --git a/tests-clar/resources/.gitignore b/tests-clar/resources/.gitignore new file mode 100644 index 000000000..43a19cc9d --- /dev/null +++ b/tests-clar/resources/.gitignore @@ -0,0 +1 @@ +discover.git diff --git a/tests/resources/empty_bare.git/HEAD b/tests-clar/resources/attr/.gitted/HEAD similarity index 100% rename from tests/resources/empty_bare.git/HEAD rename to tests-clar/resources/attr/.gitted/HEAD diff --git a/tests-clar/resources/attr/.gitted/config b/tests-clar/resources/attr/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/attr/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests/resources/empty_bare.git/description b/tests-clar/resources/attr/.gitted/description similarity index 100% rename from tests/resources/empty_bare.git/description rename to tests-clar/resources/attr/.gitted/description diff --git a/tests-clar/resources/attr/.gitted/index b/tests-clar/resources/attr/.gitted/index new file mode 100644 index 000000000..1d60eab8f Binary files /dev/null and b/tests-clar/resources/attr/.gitted/index differ diff --git a/tests-clar/resources/attr/.gitted/info/attributes b/tests-clar/resources/attr/.gitted/info/attributes new file mode 100644 index 000000000..5fe62a37a --- /dev/null +++ b/tests-clar/resources/attr/.gitted/info/attributes @@ -0,0 +1,4 @@ +* repoattr +a* foo !bar -baz +sub/*.txt reposub +sub/sub/*.txt reposubsub diff --git a/tests/resources/empty_bare.git/info/exclude b/tests-clar/resources/attr/.gitted/info/exclude similarity index 100% rename from tests/resources/empty_bare.git/info/exclude rename to tests-clar/resources/attr/.gitted/info/exclude diff --git a/tests-clar/resources/attr/.gitted/logs/HEAD b/tests-clar/resources/attr/.gitted/logs/HEAD new file mode 100644 index 000000000..73f00f345 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/logs/HEAD @@ -0,0 +1,8 @@ +0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer 1324416995 -0800 commit (initial): initial test data +6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer 1325143098 -0800 commit: latest test updates +605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer 1325281762 -0800 commit: more macro tests +a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer 1327611749 -0800 commit: Updating files so we can do diffs +370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer 1327621027 -0800 commit: Updating test data +f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer 1328653313 -0800 commit: Some whitespace only changes for testing purposes +a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer 1332734901 -0700 commit: added files in sub/sub +217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer 1332735555 -0700 commit: adding more files in sub for tree status diff --git a/tests-clar/resources/attr/.gitted/logs/refs/heads/master b/tests-clar/resources/attr/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..73f00f345 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/logs/refs/heads/master @@ -0,0 +1,8 @@ +0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer 1324416995 -0800 commit (initial): initial test data +6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer 1325143098 -0800 commit: latest test updates +605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer 1325281762 -0800 commit: more macro tests +a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer 1327611749 -0800 commit: Updating files so we can do diffs +370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer 1327621027 -0800 commit: Updating test data +f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer 1328653313 -0800 commit: Some whitespace only changes for testing purposes +a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer 1332734901 -0700 commit: added files in sub/sub +217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer 1332735555 -0700 commit: adding more files in sub for tree status diff --git a/tests-clar/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e b/tests-clar/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e new file mode 100644 index 000000000..edcf7520c Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e differ diff --git a/tests-clar/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 b/tests-clar/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 new file mode 100644 index 000000000..b537899f2 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 @@ -0,0 +1,4 @@ +xQ +0D)nD#xmvJ߀7cx0Iۺ -+e"v☝pwcJH1x%HL>Dd xC\ʤzᔶd0Z#mغڰ +y +>{qK \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b b/tests-clar/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b new file mode 100644 index 000000000..e7099bbaa Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b differ diff --git a/tests-clar/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests-clar/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b new file mode 100644 index 000000000..ad84f0854 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b differ diff --git a/tests-clar/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a b/tests-clar/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a new file mode 100644 index 000000000..0e2368069 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a @@ -0,0 +1 @@ +xmPj0=P8ZSc hR6{=ob"afv#3ά=7P%[8 yNlͣ>c;gӐkYX9b|D~Vؗ)vܕ \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 b/tests-clar/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 new file mode 100644 index 000000000..4b75d50eb Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 differ diff --git a/tests-clar/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests-clar/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 new file mode 100644 index 000000000..e0fd0468e Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 differ diff --git a/tests-clar/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a b/tests-clar/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a new file mode 100644 index 000000000..9c37c5946 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a differ diff --git a/tests-clar/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 b/tests-clar/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 new file mode 100644 index 000000000..c74add826 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 differ diff --git a/tests-clar/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests-clar/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 new file mode 100644 index 000000000..e5cef35fa Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 differ diff --git a/tests-clar/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 b/tests-clar/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 new file mode 100644 index 000000000..091d79b14 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 differ diff --git a/tests-clar/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests-clar/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d new file mode 100644 index 000000000..5b58ef024 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d differ diff --git a/tests-clar/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 b/tests-clar/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 new file mode 100644 index 000000000..f90f0d79c Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 differ diff --git a/tests/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 similarity index 100% rename from tests/resources/testrepo.git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 rename to tests-clar/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d b/tests-clar/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d new file mode 100644 index 000000000..eb1e8d0c5 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d @@ -0,0 +1,2 @@ +x @WŶ +|k 9n$}g:;51e4\k_]ރ٭hDk'~ \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 b/tests-clar/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 new file mode 100644 index 000000000..6fcc549b4 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 differ diff --git a/tests-clar/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 b/tests-clar/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 new file mode 100644 index 000000000..4bcff1faa Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 differ diff --git a/tests-clar/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests-clar/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 new file mode 100644 index 000000000..fe34eb63a Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 differ diff --git a/tests-clar/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 b/tests-clar/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 new file mode 100644 index 000000000..b0cc51ee6 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 @@ -0,0 +1,2 @@ +xN 0;S˻BU J ?lٖygcáU RbacG;l㠝Dq֠ZʫAH<Ǒ3N=J2d3[0= +}ۤI™jM"x/[TwU&[/k(tJL \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests-clar/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da new file mode 100644 index 000000000..f51e11ccc --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da @@ -0,0 +1,3 @@ +x 0 E)@d'~@(#tQiQn޷(Pm"Ř2hs L+d{"{Z`u +O4Y[޷;@>MSOmʧh +* <- \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd b/tests-clar/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd new file mode 100644 index 000000000..e832241c9 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd differ diff --git a/tests-clar/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 b/tests-clar/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 new file mode 100644 index 000000000..a80265cac Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 differ diff --git a/tests-clar/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 b/tests-clar/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 new file mode 100644 index 000000000..4b57836cd Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 differ diff --git a/tests-clar/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 b/tests-clar/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 new file mode 100644 index 000000000..a9ddf5d20 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 differ diff --git a/tests-clar/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 b/tests-clar/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 new file mode 100644 index 000000000..efa62f912 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 differ diff --git a/tests-clar/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests-clar/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 new file mode 100644 index 000000000..8f5acc70a Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 differ diff --git a/tests-clar/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 b/tests-clar/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 new file mode 100644 index 000000000..d6385ec8d Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 differ diff --git a/tests-clar/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 b/tests-clar/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 new file mode 100644 index 000000000..7663ad0ad Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 differ diff --git a/tests-clar/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests-clar/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 new file mode 100644 index 000000000..d898ae9b8 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 differ diff --git a/tests-clar/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 b/tests-clar/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 new file mode 100644 index 000000000..cd6a389f9 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 @@ -0,0 +1,4 @@ +x] +!E{vB>!"ZB;u3Cm {.7Z4avfgBLEeP;NQڬBLAnŲI 5I)M6ZQ[ +h3e: + }u};|)z&pbq?3TJ13JX \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba b/tests-clar/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba new file mode 100644 index 000000000..1a7ec0c55 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba @@ -0,0 +1,2 @@ +xQj0 DS[hc;PJ( $q޾ޠ_3oIK+BtI|Lgƈ ŐR4'=qFN64 +J1FrzW[rV6-i7.eVW;X, mwl|]ṬMɢdRwC[W9sj~Wy \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests-clar/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 new file mode 100644 index 000000000..11dc63c79 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 differ diff --git a/tests-clar/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests-clar/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c new file mode 100644 index 000000000..58569ca0e Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c differ diff --git a/tests-clar/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d b/tests-clar/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d new file mode 100644 index 000000000..39aedb7d9 Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d differ diff --git a/tests-clar/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 b/tests-clar/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 new file mode 100644 index 000000000..589f9ad31 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 @@ -0,0 +1,2 @@ +x5A +0D]SεouJ~L0ͯ)xcfp]OOΊcB 6!뢘ó{,U 1335817070 -0700 commit (initial): Initial commit +67c1640e91ccbaf0793591be09bf572cf40c9a53 d441d7d88f52c28c2b23940ce4c33756748425f9 Russell Belfer 1335817296 -0700 commit: Adding some files in subtrees +d441d7d88f52c28c2b23940ce4c33756748425f9 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer 1335817353 -0700 HEAD^: updating HEAD +67c1640e91ccbaf0793591be09bf572cf40c9a53 58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 Russell Belfer 1335817372 -0700 commit: Adding subtree data diff --git a/tests-clar/resources/attr_index/.gitted/logs/refs/heads/master b/tests-clar/resources/attr_index/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..ffd298c04 --- /dev/null +++ b/tests-clar/resources/attr_index/.gitted/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer 1335817070 -0700 commit (initial): Initial commit +67c1640e91ccbaf0793591be09bf572cf40c9a53 d441d7d88f52c28c2b23940ce4c33756748425f9 Russell Belfer 1335817296 -0700 commit: Adding some files in subtrees +d441d7d88f52c28c2b23940ce4c33756748425f9 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer 1335817353 -0700 HEAD^: updating HEAD +67c1640e91ccbaf0793591be09bf572cf40c9a53 58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 Russell Belfer 1335817372 -0700 commit: Adding subtree data diff --git a/tests-clar/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 b/tests-clar/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 new file mode 100644 index 000000000..ee2991571 --- /dev/null +++ b/tests-clar/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 @@ -0,0 +1,3 @@ +x[ + E*@PJ +]&`w 'oMKDq45DF#!!=VZD.L:%L}!dCg*qв-LUkz~6閷W}}>g˾{f% G \ No newline at end of file diff --git a/tests-clar/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b b/tests-clar/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b new file mode 100644 index 000000000..ff33737db --- /dev/null +++ b/tests-clar/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b @@ -0,0 +1 @@ +x 0 @@kR@]\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/tests-clar/resources/duplicate.git/hooks/post-update.sample b/tests-clar/resources/duplicate.git/hooks/post-update.sample new file mode 100755 index 000000000..ec17ec193 --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/tests-clar/resources/duplicate.git/hooks/pre-applypatch.sample b/tests-clar/resources/duplicate.git/hooks/pre-applypatch.sample new file mode 100755 index 000000000..b1f187c2e --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} +: diff --git a/tests-clar/resources/duplicate.git/hooks/pre-commit.sample b/tests-clar/resources/duplicate.git/hooks/pre-commit.sample new file mode 100755 index 000000000..18c482976 --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/pre-commit.sample @@ -0,0 +1,50 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ascii filenames set this variable to true. +allownonascii=$(git config hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ascii filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + echo "Error: Attempt to add a non-ascii file name." + echo + echo "This can cause problems if you want to work" + echo "with people on other platforms." + echo + echo "To be portable it is advisable to rename the file ..." + echo + echo "If you know what you are doing you can disable this" + echo "check using:" + echo + echo " git config hooks.allownonascii true" + echo + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/tests-clar/resources/duplicate.git/hooks/pre-rebase.sample b/tests-clar/resources/duplicate.git/hooks/pre-rebase.sample new file mode 100755 index 000000000..9773ed4cb --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/tests-clar/resources/duplicate.git/hooks/prepare-commit-msg.sample b/tests-clar/resources/duplicate.git/hooks/prepare-commit-msg.sample new file mode 100755 index 000000000..f093a02ec --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/tests-clar/resources/duplicate.git/hooks/update.sample b/tests-clar/resources/duplicate.git/hooks/update.sample new file mode 100755 index 000000000..71ab04edc --- /dev/null +++ b/tests-clar/resources/duplicate.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to blocks unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/tests-clar/resources/duplicate.git/index b/tests-clar/resources/duplicate.git/index new file mode 100644 index 000000000..a61e1c5ca Binary files /dev/null and b/tests-clar/resources/duplicate.git/index differ diff --git a/tests-clar/resources/duplicate.git/info/exclude b/tests-clar/resources/duplicate.git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/duplicate.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/duplicate.git/info/refs b/tests-clar/resources/duplicate.git/info/refs new file mode 100644 index 000000000..3b5bb03be --- /dev/null +++ b/tests-clar/resources/duplicate.git/info/refs @@ -0,0 +1 @@ +8d2f05c97ef29a4697b37c30fe81c248ef411a23 refs/heads/master diff --git a/tests-clar/resources/duplicate.git/logs/HEAD b/tests-clar/resources/duplicate.git/logs/HEAD new file mode 100644 index 000000000..be9b4c6cb --- /dev/null +++ b/tests-clar/resources/duplicate.git/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 8d2f05c97ef29a4697b37c30fe81c248ef411a23 Han-Wen Nienhuys 1336844322 -0300 commit (initial): commit diff --git a/tests-clar/resources/duplicate.git/logs/refs/heads/master b/tests-clar/resources/duplicate.git/logs/refs/heads/master new file mode 100644 index 000000000..be9b4c6cb --- /dev/null +++ b/tests-clar/resources/duplicate.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 8d2f05c97ef29a4697b37c30fe81c248ef411a23 Han-Wen Nienhuys 1336844322 -0300 commit (initial): commit diff --git a/tests-clar/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/tests-clar/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a new file mode 100644 index 000000000..6802d4949 Binary files /dev/null and b/tests-clar/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a differ diff --git a/tests-clar/resources/duplicate.git/objects/info/packs b/tests-clar/resources/duplicate.git/objects/info/packs new file mode 100644 index 000000000..3696a7d36 --- /dev/null +++ b/tests-clar/resources/duplicate.git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-e87994ad581c9af946de0eb890175c08cd005f38.pack + diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx b/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx new file mode 100644 index 000000000..fd8abee98 Binary files /dev/null and b/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx differ diff --git a/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack b/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack new file mode 100644 index 000000000..9879e0869 Binary files /dev/null and b/tests-clar/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack differ diff --git a/tests-clar/resources/duplicate.git/packed-refs b/tests-clar/resources/duplicate.git/packed-refs new file mode 100644 index 000000000..9f0d4e434 --- /dev/null +++ b/tests-clar/resources/duplicate.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +8d2f05c97ef29a4697b37c30fe81c248ef411a23 refs/heads/master diff --git a/tests-clar/resources/empty_bare.git/HEAD b/tests-clar/resources/empty_bare.git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/empty_bare.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/resources/empty_bare.git/config b/tests-clar/resources/empty_bare.git/config similarity index 100% rename from tests/resources/empty_bare.git/config rename to tests-clar/resources/empty_bare.git/config diff --git a/tests-clar/resources/empty_bare.git/description b/tests-clar/resources/empty_bare.git/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/empty_bare.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/empty_bare.git/info/exclude b/tests-clar/resources/empty_bare.git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/empty_bare.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt b/tests-clar/resources/empty_bare.git/objects/info/dummy-marker.txt similarity index 100% rename from tests/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt rename to tests-clar/resources/empty_bare.git/objects/info/dummy-marker.txt diff --git a/tests/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt b/tests-clar/resources/empty_bare.git/objects/pack/dummy-marker.txt similarity index 100% rename from tests/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt rename to tests-clar/resources/empty_bare.git/objects/pack/dummy-marker.txt diff --git a/tests/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt b/tests-clar/resources/empty_bare.git/refs/heads/dummy-marker.txt similarity index 100% rename from tests/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt rename to tests-clar/resources/empty_bare.git/refs/heads/dummy-marker.txt diff --git a/tests/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt b/tests-clar/resources/empty_bare.git/refs/tags/dummy-marker.txt similarity index 100% rename from tests/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt rename to tests-clar/resources/empty_bare.git/refs/tags/dummy-marker.txt diff --git a/tests-clar/resources/empty_standard_repo/.gitted/HEAD b/tests-clar/resources/empty_standard_repo/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/empty_standard_repo/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/resources/empty_standard_repo/.gitted/config b/tests-clar/resources/empty_standard_repo/.gitted/config similarity index 100% rename from tests/resources/empty_standard_repo/.gitted/config rename to tests-clar/resources/empty_standard_repo/.gitted/config diff --git a/tests-clar/resources/empty_standard_repo/.gitted/description b/tests-clar/resources/empty_standard_repo/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/empty_standard_repo/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/empty_standard_repo/.gitted/info/exclude b/tests-clar/resources/empty_standard_repo/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/empty_standard_repo/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt b/tests-clar/resources/empty_standard_repo/.gitted/objects/info/dummy-marker.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests-clar/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt b/tests-clar/resources/empty_standard_repo/.gitted/objects/pack/dummy-marker.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests-clar/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt b/tests-clar/resources/empty_standard_repo/.gitted/refs/heads/dummy-marker.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests-clar/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt b/tests-clar/resources/empty_standard_repo/.gitted/refs/tags/dummy-marker.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/resources/gitgit.index b/tests-clar/resources/gitgit.index similarity index 100% rename from tests/resources/gitgit.index rename to tests-clar/resources/gitgit.index diff --git a/tests-clar/resources/issue_592/.gitted/COMMIT_EDITMSG b/tests-clar/resources/issue_592/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..5852f4463 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial commit diff --git a/tests-clar/resources/issue_592/.gitted/HEAD b/tests-clar/resources/issue_592/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/issue_592/.gitted/config b/tests-clar/resources/issue_592/.gitted/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/tests-clar/resources/issue_592/.gitted/index b/tests-clar/resources/issue_592/.gitted/index new file mode 100644 index 000000000..eaeb5d761 Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/index differ diff --git a/tests-clar/resources/issue_592/.gitted/info/exclude b/tests-clar/resources/issue_592/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/issue_592/.gitted/logs/HEAD b/tests-clar/resources/issue_592/.gitted/logs/HEAD new file mode 100644 index 000000000..f19fe35a6 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken 1331989635 +0100 commit (initial): Initial commit +4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken 1332227062 +0100 commit (amend): Initial commit diff --git a/tests-clar/resources/issue_592/.gitted/logs/refs/heads/master b/tests-clar/resources/issue_592/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..f19fe35a6 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken 1331989635 +0100 commit (initial): Initial commit +4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken 1332227062 +0100 commit (amend): Initial commit diff --git a/tests-clar/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e b/tests-clar/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e new file mode 100644 index 000000000..05dec10f7 Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e differ diff --git a/tests-clar/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 b/tests-clar/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 new file mode 100644 index 000000000..e997e1b49 Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 differ diff --git a/tests-clar/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 b/tests-clar/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 new file mode 100644 index 000000000..c49a8be58 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 @@ -0,0 +1,2 @@ +xM +0]o/IDz bz H9v2t =k,p+͏>4U=^(tAF2̟3sL|%cYuZv{=r_G}K> \ No newline at end of file diff --git a/tests-clar/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 b/tests-clar/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 new file mode 100644 index 000000000..25d44d938 Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 differ diff --git a/tests-clar/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 b/tests-clar/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 new file mode 100644 index 000000000..1d6e38d37 Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 differ diff --git a/tests-clar/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 b/tests-clar/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 new file mode 100644 index 000000000..36c5b9aab Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 differ diff --git a/tests-clar/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 b/tests-clar/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 new file mode 100644 index 000000000..c08ecd5ed Binary files /dev/null and b/tests-clar/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 differ diff --git a/tests-clar/resources/issue_592/.gitted/refs/heads/master b/tests-clar/resources/issue_592/.gitted/refs/heads/master new file mode 100644 index 000000000..1f6669628 --- /dev/null +++ b/tests-clar/resources/issue_592/.gitted/refs/heads/master @@ -0,0 +1 @@ +e38fcc7a6060f5eb5b876e836b52ae4769363f21 diff --git a/tests-clar/resources/issue_592/a.txt b/tests-clar/resources/issue_592/a.txt new file mode 100644 index 000000000..f1adef63c --- /dev/null +++ b/tests-clar/resources/issue_592/a.txt @@ -0,0 +1 @@ +nothing here diff --git a/tests-clar/resources/issue_592/c/a.txt b/tests-clar/resources/issue_592/c/a.txt new file mode 100644 index 000000000..f1adef63c --- /dev/null +++ b/tests-clar/resources/issue_592/c/a.txt @@ -0,0 +1 @@ +nothing here diff --git a/tests-clar/resources/issue_592/l.txt b/tests-clar/resources/issue_592/l.txt new file mode 100644 index 000000000..f1adef63c --- /dev/null +++ b/tests-clar/resources/issue_592/l.txt @@ -0,0 +1 @@ +nothing here diff --git a/tests-clar/resources/issue_592/t/a.txt b/tests-clar/resources/issue_592/t/a.txt new file mode 100644 index 000000000..f1adef63c --- /dev/null +++ b/tests-clar/resources/issue_592/t/a.txt @@ -0,0 +1 @@ +nothing here diff --git a/tests-clar/resources/issue_592/t/b.txt b/tests-clar/resources/issue_592/t/b.txt new file mode 100644 index 000000000..f1adef63c --- /dev/null +++ b/tests-clar/resources/issue_592/t/b.txt @@ -0,0 +1 @@ +nothing here diff --git a/tests-clar/resources/issue_592b/.gitted/HEAD b/tests-clar/resources/issue_592b/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/issue_592b/.gitted/config b/tests-clar/resources/issue_592b/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/issue_592b/.gitted/description b/tests-clar/resources/issue_592b/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/issue_592b/.gitted/hooks/post-update.sample b/tests-clar/resources/issue_592b/.gitted/hooks/post-update.sample new file mode 100755 index 000000000..ec17ec193 --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/tests-clar/resources/issue_592b/.gitted/index b/tests-clar/resources/issue_592b/.gitted/index new file mode 100644 index 000000000..596438216 Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/index differ diff --git a/tests-clar/resources/issue_592b/.gitted/info/exclude b/tests-clar/resources/issue_592b/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/issue_592b/.gitted/logs/HEAD b/tests-clar/resources/issue_592b/.gitted/logs/HEAD new file mode 100644 index 000000000..6f3ba90cc --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 3fbf1852f72fd268e36457b13a18cdd9a4c9ea35 Russell Belfer 1337205933 -0700 commit (initial): Initial commit diff --git a/tests-clar/resources/issue_592b/.gitted/logs/refs/heads/master b/tests-clar/resources/issue_592b/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..6f3ba90cc --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 3fbf1852f72fd268e36457b13a18cdd9a4c9ea35 Russell Belfer 1337205933 -0700 commit (initial): Initial commit diff --git a/tests-clar/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 b/tests-clar/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 new file mode 100644 index 000000000..6eaf64b46 --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 @@ -0,0 +1,2 @@ +xK +1]}%BwnAq xzVƃv ɂc&%9@9xdu.]".=EבO+ۘBEk\N_<>E U%9 \ No newline at end of file diff --git a/tests-clar/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f b/tests-clar/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f new file mode 100644 index 000000000..c4becfe2f Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f differ diff --git a/tests-clar/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 b/tests-clar/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 new file mode 100644 index 000000000..aea14f2af Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 differ diff --git a/tests-clar/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 b/tests-clar/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 new file mode 100644 index 000000000..9b7407221 Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 differ diff --git a/tests-clar/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c b/tests-clar/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c new file mode 100644 index 000000000..1494ed822 Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c differ diff --git a/tests-clar/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc b/tests-clar/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc new file mode 100644 index 000000000..7a6626636 Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc differ diff --git a/tests-clar/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 b/tests-clar/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 new file mode 100644 index 000000000..65a1fd0d0 Binary files /dev/null and b/tests-clar/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 differ diff --git a/tests-clar/resources/issue_592b/.gitted/refs/heads/master b/tests-clar/resources/issue_592b/.gitted/refs/heads/master new file mode 100644 index 000000000..c0a9ab495 --- /dev/null +++ b/tests-clar/resources/issue_592b/.gitted/refs/heads/master @@ -0,0 +1 @@ +3fbf1852f72fd268e36457b13a18cdd9a4c9ea35 diff --git a/tests-clar/resources/issue_592b/gitignore b/tests-clar/resources/issue_592b/gitignore new file mode 100644 index 000000000..8007d41d5 --- /dev/null +++ b/tests-clar/resources/issue_592b/gitignore @@ -0,0 +1 @@ +ignored/ diff --git a/tests-clar/resources/issue_592b/ignored/contained/ignored3.txt b/tests-clar/resources/issue_592b/ignored/contained/ignored3.txt new file mode 100644 index 000000000..b5dc7b073 --- /dev/null +++ b/tests-clar/resources/issue_592b/ignored/contained/ignored3.txt @@ -0,0 +1 @@ +I'm ignored diff --git a/tests-clar/resources/issue_592b/ignored/contained/tracked3.txt b/tests-clar/resources/issue_592b/ignored/contained/tracked3.txt new file mode 100644 index 000000000..b344b0558 --- /dev/null +++ b/tests-clar/resources/issue_592b/ignored/contained/tracked3.txt @@ -0,0 +1 @@ +You added me anyhow diff --git a/tests-clar/resources/issue_592b/ignored/ignored2.txt b/tests-clar/resources/issue_592b/ignored/ignored2.txt new file mode 100644 index 000000000..b5dc7b073 --- /dev/null +++ b/tests-clar/resources/issue_592b/ignored/ignored2.txt @@ -0,0 +1 @@ +I'm ignored diff --git a/tests-clar/resources/issue_592b/ignored/tracked2.txt b/tests-clar/resources/issue_592b/ignored/tracked2.txt new file mode 100644 index 000000000..6fa891d3e --- /dev/null +++ b/tests-clar/resources/issue_592b/ignored/tracked2.txt @@ -0,0 +1 @@ +You like me diff --git a/tests-clar/resources/issue_592b/ignored1.txt b/tests-clar/resources/issue_592b/ignored1.txt new file mode 100644 index 000000000..b5dc7b073 --- /dev/null +++ b/tests-clar/resources/issue_592b/ignored1.txt @@ -0,0 +1 @@ +I'm ignored diff --git a/tests-clar/resources/issue_592b/tracked1.txt b/tests-clar/resources/issue_592b/tracked1.txt new file mode 100644 index 000000000..6fa891d3e --- /dev/null +++ b/tests-clar/resources/issue_592b/tracked1.txt @@ -0,0 +1 @@ +You like me diff --git a/tests-clar/resources/status/.gitted/COMMIT_EDITMSG b/tests-clar/resources/status/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..1a25cd4a6 --- /dev/null +++ b/tests-clar/resources/status/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker diff --git a/tests-clar/resources/status/.gitted/HEAD b/tests-clar/resources/status/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/status/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/status/.gitted/ORIG_HEAD b/tests-clar/resources/status/.gitted/ORIG_HEAD new file mode 100644 index 000000000..b46871fd6 --- /dev/null +++ b/tests-clar/resources/status/.gitted/ORIG_HEAD @@ -0,0 +1 @@ +735b6a258cd196a8f7c9428419b02c1dca93fd75 diff --git a/tests-clar/resources/status/.gitted/config b/tests-clar/resources/status/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/status/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/status/.gitted/description b/tests-clar/resources/status/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/status/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/status/.gitted/index b/tests-clar/resources/status/.gitted/index new file mode 100644 index 000000000..9a383ec0c Binary files /dev/null and b/tests-clar/resources/status/.gitted/index differ diff --git a/tests-clar/resources/status/.gitted/info/exclude b/tests-clar/resources/status/.gitted/info/exclude new file mode 100644 index 000000000..0c4042a6a --- /dev/null +++ b/tests-clar/resources/status/.gitted/info/exclude @@ -0,0 +1,8 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +ignored* + diff --git a/tests-clar/resources/status/.gitted/logs/HEAD b/tests-clar/resources/status/.gitted/logs/HEAD new file mode 100644 index 000000000..7b95b3cf1 --- /dev/null +++ b/tests-clar/resources/status/.gitted/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 0017bd4ab1ec30440b17bae1680cff124ab5f1f6 Jason Penny 1308050070 -0400 commit (initial): initial +0017bd4ab1ec30440b17bae1680cff124ab5f1f6 735b6a258cd196a8f7c9428419b02c1dca93fd75 Jason Penny 1308954538 -0400 commit: add subdir +735b6a258cd196a8f7c9428419b02c1dca93fd75 26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f nulltoken 1319911544 +0200 commit: Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker diff --git a/tests-clar/resources/status/.gitted/logs/refs/heads/master b/tests-clar/resources/status/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..7b95b3cf1 --- /dev/null +++ b/tests-clar/resources/status/.gitted/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 0017bd4ab1ec30440b17bae1680cff124ab5f1f6 Jason Penny 1308050070 -0400 commit (initial): initial +0017bd4ab1ec30440b17bae1680cff124ab5f1f6 735b6a258cd196a8f7c9428419b02c1dca93fd75 Jason Penny 1308954538 -0400 commit: add subdir +735b6a258cd196a8f7c9428419b02c1dca93fd75 26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f nulltoken 1319911544 +0200 commit: Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker diff --git a/tests-clar/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 b/tests-clar/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 new file mode 100644 index 000000000..b256d95a3 --- /dev/null +++ b/tests-clar/resources/status/.gitted/objects/00/17bd4ab1ec30440b17bae1680cff124ab5f1f6 @@ -0,0 +1,2 @@ +xA E]sfh)1] +#STWpK^~9ܡ-"C'؅)Fvbv "wEk{nRί6#sO pD663WxV?9 \ No newline at end of file diff --git a/tests-clar/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 b/tests-clar/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 new file mode 100644 index 000000000..82e02cb0e Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/06/1d42a44cacde5726057b67558821d95db96f19 differ diff --git a/tests-clar/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 b/tests-clar/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 new file mode 100644 index 000000000..e3cad2f02 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/18/88c805345ba265b0ee9449b8877b6064592058 differ diff --git a/tests-clar/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 b/tests-clar/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 new file mode 100644 index 000000000..2d5e711b9 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/19/d9cc8584ac2c7dcf57d2680375e80f099dc481 differ diff --git a/tests-clar/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f b/tests-clar/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f new file mode 100644 index 000000000..f7dddc4ff --- /dev/null +++ b/tests-clar/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f @@ -0,0 +1,2 @@ +xMn )V (̀BD޾LЍRȷވ@,9̜tNj6f`M6Z;h Zp ڙY,37/;42x&gϟۉIm|j \ No newline at end of file diff --git a/tests-clar/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c b/tests-clar/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c new file mode 100644 index 000000000..7fca67be8 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/32/504b727382542f9f089e24fddac5e78533e96c differ diff --git a/tests-clar/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 b/tests-clar/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 new file mode 100644 index 000000000..b75481b51 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 differ diff --git a/tests-clar/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a b/tests-clar/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a new file mode 100644 index 000000000..5b47461e9 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/45/2e4244b5d083ddf0460acf1ecc74db9dcfa11a differ diff --git a/tests-clar/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a b/tests-clar/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a new file mode 100644 index 000000000..615009ad0 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/52/9a16e8e762d4acb7b9636ff540a00831f9155a differ diff --git a/tests-clar/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 b/tests-clar/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 new file mode 100644 index 000000000..cdb7e961a Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/53/ace0d1cc1145a5f4fe4f78a186a60263190733 differ diff --git a/tests-clar/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f b/tests-clar/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f new file mode 100644 index 000000000..a72dff646 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/54/52d32f1dd538eb0405e8a83cc185f79e25e80f differ diff --git a/tests-clar/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 b/tests-clar/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 new file mode 100644 index 000000000..72807f3d0 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/55/d316c9ba708999f1918e9677d01dfcae69c6b9 differ diff --git a/tests-clar/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 b/tests-clar/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 new file mode 100644 index 000000000..3665a8f7c Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/70/bd9443ada07063e7fbf0b3ff5c13f7494d89c2 differ diff --git a/tests-clar/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 b/tests-clar/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 new file mode 100644 index 000000000..08e6fd246 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/73/5b6a258cd196a8f7c9428419b02c1dca93fd75 differ diff --git a/tests-clar/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea b/tests-clar/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea new file mode 100644 index 000000000..8f3fa89e5 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/75/6e27627e67bfbc048d01ece5819c6de733d7ea differ diff --git a/tests-clar/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 b/tests-clar/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 new file mode 100644 index 000000000..bb732b08e Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/90/6ee7711f4f4928ddcb2a5f8fbc500deba0d2a8 differ diff --git a/tests-clar/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 b/tests-clar/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 new file mode 100644 index 000000000..7a96618ff Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/90/b8c29d8ba39434d1c63e1b093daaa26e5bd972 differ diff --git a/tests-clar/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 b/tests-clar/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 new file mode 100644 index 000000000..20a3c497e Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/9c/2e02cdffa8d73e6c189074594477a6baf87960 differ diff --git a/tests-clar/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e b/tests-clar/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e new file mode 100644 index 000000000..a1789c9a6 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/a0/de7e0ac200c489c41c59dfa910154a70264e6e differ diff --git a/tests-clar/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 b/tests-clar/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 new file mode 100644 index 000000000..cc1f377b3 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/a6/191982709b746d5650e93c2acf34ef74e11504 differ diff --git a/tests-clar/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b b/tests-clar/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b new file mode 100644 index 000000000..c47298347 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/a6/be623522ce87a1d862128ac42672604f7b468b differ diff --git a/tests-clar/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 b/tests-clar/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 new file mode 100644 index 000000000..a4669ccbb Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/aa/27a641456848200fdb7f7c99ba36f8a0952877 differ diff --git a/tests-clar/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 b/tests-clar/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 new file mode 100644 index 000000000..3e3c03c96 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/da/bc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 differ diff --git a/tests-clar/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e b/tests-clar/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e new file mode 100644 index 000000000..cfc2413d5 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e differ diff --git a/tests-clar/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca b/tests-clar/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca new file mode 100644 index 000000000..1266d3eac Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/e9/b9107f290627c04d097733a10055af941f6bca differ diff --git a/tests-clar/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd b/tests-clar/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd new file mode 100644 index 000000000..8fa8c1707 Binary files /dev/null and b/tests-clar/resources/status/.gitted/objects/ed/062903b8f6f3dccb2fa81117ba6590944ef9bd differ diff --git a/tests-clar/resources/status/.gitted/refs/heads/master b/tests-clar/resources/status/.gitted/refs/heads/master new file mode 100644 index 000000000..3e2e2a07a --- /dev/null +++ b/tests-clar/resources/status/.gitted/refs/heads/master @@ -0,0 +1 @@ +26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f diff --git a/tests-clar/resources/status/current_file b/tests-clar/resources/status/current_file new file mode 100644 index 000000000..a0de7e0ac --- /dev/null +++ b/tests-clar/resources/status/current_file @@ -0,0 +1 @@ +current_file diff --git a/tests-clar/resources/status/ignored_file b/tests-clar/resources/status/ignored_file new file mode 100644 index 000000000..6a79f808a --- /dev/null +++ b/tests-clar/resources/status/ignored_file @@ -0,0 +1 @@ +ignored_file diff --git a/tests-clar/resources/status/modified_file b/tests-clar/resources/status/modified_file new file mode 100644 index 000000000..0a5396305 --- /dev/null +++ b/tests-clar/resources/status/modified_file @@ -0,0 +1,2 @@ +modified_file +modified_file diff --git a/tests-clar/resources/status/new_file b/tests-clar/resources/status/new_file new file mode 100644 index 000000000..d4fa8600b --- /dev/null +++ b/tests-clar/resources/status/new_file @@ -0,0 +1 @@ +new_file diff --git a/tests-clar/resources/status/staged_changes b/tests-clar/resources/status/staged_changes new file mode 100644 index 000000000..55d316c9b --- /dev/null +++ b/tests-clar/resources/status/staged_changes @@ -0,0 +1,2 @@ +staged_changes +staged_changes diff --git a/tests-clar/resources/status/staged_changes_modified_file b/tests-clar/resources/status/staged_changes_modified_file new file mode 100644 index 000000000..011c3440d --- /dev/null +++ b/tests-clar/resources/status/staged_changes_modified_file @@ -0,0 +1,3 @@ +staged_changes_modified_file +staged_changes_modified_file +staged_changes_modified_file diff --git a/tests-clar/resources/status/staged_delete_modified_file b/tests-clar/resources/status/staged_delete_modified_file new file mode 100644 index 000000000..dabc8af9b --- /dev/null +++ b/tests-clar/resources/status/staged_delete_modified_file @@ -0,0 +1 @@ +staged_delete_modified_file diff --git a/tests-clar/resources/status/staged_new_file b/tests-clar/resources/status/staged_new_file new file mode 100644 index 000000000..529a16e8e --- /dev/null +++ b/tests-clar/resources/status/staged_new_file @@ -0,0 +1 @@ +staged_new_file diff --git a/tests-clar/resources/status/staged_new_file_modified_file b/tests-clar/resources/status/staged_new_file_modified_file new file mode 100644 index 000000000..8b090c06d --- /dev/null +++ b/tests-clar/resources/status/staged_new_file_modified_file @@ -0,0 +1,2 @@ +staged_new_file_modified_file +staged_new_file_modified_file diff --git a/tests-clar/resources/status/subdir.txt b/tests-clar/resources/status/subdir.txt new file mode 100644 index 000000000..e8ee89e15 --- /dev/null +++ b/tests-clar/resources/status/subdir.txt @@ -0,0 +1,2 @@ +Is it a bird? +Is it a plane? diff --git a/tests-clar/resources/status/subdir/current_file b/tests-clar/resources/status/subdir/current_file new file mode 100644 index 000000000..53ace0d1c --- /dev/null +++ b/tests-clar/resources/status/subdir/current_file @@ -0,0 +1 @@ +subdir/current_file diff --git a/tests-clar/resources/status/subdir/modified_file b/tests-clar/resources/status/subdir/modified_file new file mode 100644 index 000000000..57274b75e --- /dev/null +++ b/tests-clar/resources/status/subdir/modified_file @@ -0,0 +1,2 @@ +subdir/modified_file +subdir/modified_file diff --git a/tests-clar/resources/status/subdir/new_file b/tests-clar/resources/status/subdir/new_file new file mode 100644 index 000000000..80a86a693 --- /dev/null +++ b/tests-clar/resources/status/subdir/new_file @@ -0,0 +1 @@ +subdir/new_file diff --git a/tests-clar/resources/status/这 b/tests-clar/resources/status/这 new file mode 100644 index 000000000..f0ff9a197 --- /dev/null +++ b/tests-clar/resources/status/这 @@ -0,0 +1 @@ +This diff --git a/tests-clar/resources/submodules/.gitted/HEAD b/tests-clar/resources/submodules/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submodules/.gitted/config b/tests-clar/resources/submodules/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/submodules/.gitted/description b/tests-clar/resources/submodules/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submodules/.gitted/index b/tests-clar/resources/submodules/.gitted/index new file mode 100644 index 000000000..97bf8ef51 Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/index differ diff --git a/tests-clar/resources/submodules/.gitted/info/exclude b/tests-clar/resources/submodules/.gitted/info/exclude new file mode 100644 index 000000000..dfc411579 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/info/exclude @@ -0,0 +1,8 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ +ignored + diff --git a/tests-clar/resources/submodules/.gitted/info/refs b/tests-clar/resources/submodules/.gitted/info/refs new file mode 100644 index 000000000..ba17abdde --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/info/refs @@ -0,0 +1 @@ +09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master diff --git a/tests-clar/resources/submodules/.gitted/logs/HEAD b/tests-clar/resources/submodules/.gitted/logs/HEAD new file mode 100644 index 000000000..87a7bdafc --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer 1332365253 -0700 commit (initial): initial commit +09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer 1332780781 -0700 commit: Setting up gitmodules diff --git a/tests-clar/resources/submodules/.gitted/logs/refs/heads/master b/tests-clar/resources/submodules/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..87a7bdafc --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer 1332365253 -0700 commit (initial): initial commit +09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer 1332780781 -0700 commit: Setting up gitmodules diff --git a/tests-clar/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e b/tests-clar/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e new file mode 100644 index 000000000..2c3c2cb61 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e @@ -0,0 +1,2 @@ +x%= +0 F])0"I*|-t{?2ilV8$mvkk*F DA=(=|=6 DAv=A}&'O$= \ No newline at end of file diff --git a/tests-clar/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 b/tests-clar/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 new file mode 100644 index 000000000..c85fb5512 Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 differ diff --git a/tests-clar/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 b/tests-clar/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 new file mode 100644 index 000000000..1c8dbdf9f --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 @@ -0,0 +1,3 @@ +x[ +0E*fʤS K4ݿwׅ9p2MCFP @u..p!OYdiYU'̕8XbPn6 +ħԞ1[q}0qc[W#1fR:SZ+Y+{tdlvOmu_}5i` K \ No newline at end of file diff --git a/tests-clar/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 b/tests-clar/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 new file mode 100644 index 000000000..3d78bd6be Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 differ diff --git a/tests-clar/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 b/tests-clar/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 new file mode 100644 index 000000000..6e0b49e86 Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 differ diff --git a/tests-clar/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae b/tests-clar/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae new file mode 100644 index 000000000..082a58941 Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae differ diff --git a/tests-clar/resources/submodules/.gitted/objects/info/packs b/tests-clar/resources/submodules/.gitted/objects/info/packs new file mode 100644 index 000000000..0785ef698 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/objects/info/packs @@ -0,0 +1,2 @@ +P pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack + diff --git a/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx b/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx new file mode 100644 index 000000000..810fc3181 Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx differ diff --git a/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack b/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack new file mode 100644 index 000000000..c25c4a73f Binary files /dev/null and b/tests-clar/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack differ diff --git a/tests-clar/resources/submodules/.gitted/packed-refs b/tests-clar/resources/submodules/.gitted/packed-refs new file mode 100644 index 000000000..a6450691e --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master diff --git a/tests-clar/resources/submodules/.gitted/refs/heads/master b/tests-clar/resources/submodules/.gitted/refs/heads/master new file mode 100644 index 000000000..32b935853 --- /dev/null +++ b/tests-clar/resources/submodules/.gitted/refs/heads/master @@ -0,0 +1 @@ +97896810b3210244a62a82458b8e0819ecfc6850 diff --git a/tests-clar/resources/submodules/added b/tests-clar/resources/submodules/added new file mode 100644 index 000000000..d5f7fc3f7 --- /dev/null +++ b/tests-clar/resources/submodules/added @@ -0,0 +1 @@ +added diff --git a/tests-clar/resources/submodules/gitmodules b/tests-clar/resources/submodules/gitmodules new file mode 100644 index 000000000..1262f8bb0 --- /dev/null +++ b/tests-clar/resources/submodules/gitmodules @@ -0,0 +1,3 @@ +[submodule "testrepo"] + path = testrepo + url = \ No newline at end of file diff --git a/tests-clar/resources/submodules/ignored b/tests-clar/resources/submodules/ignored new file mode 100644 index 000000000..092bfb9bd --- /dev/null +++ b/tests-clar/resources/submodules/ignored @@ -0,0 +1 @@ +yo diff --git a/tests-clar/resources/submodules/modified b/tests-clar/resources/submodules/modified new file mode 100644 index 000000000..452216e1d --- /dev/null +++ b/tests-clar/resources/submodules/modified @@ -0,0 +1,2 @@ +changed + diff --git a/tests-clar/resources/submodules/testrepo/.gitted/HEAD b/tests-clar/resources/submodules/testrepo/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submodules/testrepo/.gitted/config b/tests-clar/resources/submodules/testrepo/.gitted/config new file mode 100644 index 000000000..d6dcad12b --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/config @@ -0,0 +1,12 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests/resources/testrepo.git +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submodules/testrepo/.gitted/description b/tests-clar/resources/submodules/testrepo/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submodules/testrepo/.gitted/index b/tests-clar/resources/submodules/testrepo/.gitted/index new file mode 100644 index 000000000..3eb8d84fe Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/index differ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/info/exclude b/tests-clar/resources/submodules/testrepo/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/logs/HEAD b/tests-clar/resources/submodules/testrepo/.gitted/logs/HEAD new file mode 100644 index 000000000..147643a30 --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git diff --git a/tests-clar/resources/submodules/testrepo/.gitted/logs/refs/heads/master b/tests-clar/resources/submodules/testrepo/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..147643a30 --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git diff --git a/tests/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests-clar/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 similarity index 100% rename from tests/resources/testrepo.git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests-clar/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 similarity index 100% rename from tests/resources/testrepo.git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests-clar/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd similarity index 100% rename from tests/resources/testrepo.git/objects/18/10dff58d8a660512d4832e740f692884338ccd rename to tests-clar/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests-clar/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b new file mode 100644 index 000000000..225c45734 Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b differ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests-clar/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d new file mode 100644 index 000000000..df40d99af Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d differ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests-clar/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 new file mode 100644 index 000000000..321eaa867 Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 differ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests-clar/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc new file mode 100644 index 000000000..9bb5b623b Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc differ diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 new file mode 100644 index 000000000..7ca4ceed5 Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 differ diff --git a/tests/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests-clar/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 similarity index 100% rename from tests/resources/testrepo.git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 diff --git a/tests/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests-clar/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 similarity index 100% rename from tests/resources/testrepo.git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 diff --git a/tests/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a similarity index 100% rename from tests/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a rename to tests-clar/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests-clar/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af new file mode 100644 index 000000000..716b0c64b --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af @@ -0,0 +1 @@ +xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR, \ No newline at end of file diff --git a/tests/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests-clar/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 similarity index 100% rename from tests/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 diff --git a/tests/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d similarity index 100% rename from tests/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d rename to tests-clar/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 similarity index 100% rename from tests/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 rename to tests-clar/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests-clar/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 new file mode 100644 index 000000000..4cc3f4dff --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 @@ -0,0 +1 @@ +x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1X \ No newline at end of file diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests-clar/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 new file mode 100644 index 000000000..bf7b2bb68 Binary files /dev/null and b/tests-clar/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 differ diff --git a/tests/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a similarity index 100% rename from tests/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a rename to tests-clar/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f similarity index 100% rename from tests/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f rename to tests-clar/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests-clar/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 new file mode 100644 index 000000000..29c8e824d --- /dev/null +++ b/tests-clar/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 @@ -0,0 +1,3 @@ +xQ +!@sBQ" ٱ r{ +F- \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a new file mode 100644 index 000000000..2ef4faa0f Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a differ diff --git a/tests-clar/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests-clar/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af new file mode 100644 index 000000000..716b0c64b --- /dev/null +++ b/tests-clar/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af @@ -0,0 +1 @@ +xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR, \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests-clar/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/tests-clar/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d new file mode 100644 index 000000000..2f9b6b6e3 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d differ diff --git a/tests-clar/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 new file mode 100644 index 000000000..5df58dda5 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/84/96071c1b46c854b31185ea97743be6a8774479 differ diff --git a/tests-clar/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests-clar/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 new file mode 100644 index 000000000..4cc3f4dff --- /dev/null +++ b/tests-clar/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 @@ -0,0 +1 @@ +x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1X \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests-clar/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 new file mode 100644 index 000000000..bf7b2bb68 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 differ diff --git a/tests-clar/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a new file mode 100644 index 000000000..a79612435 --- /dev/null +++ b/tests-clar/resources/testrepo.git/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a @@ -0,0 +1,3 @@ +x[ +0E*fդ "W0-Ft݁pS[Yx^ +Db CLhut}8X*4ZsYUA X3RM) s6輢Mរ&Jm;}<\@ޏpĀv?jۺL?H \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f new file mode 100644 index 000000000..f8588696b --- /dev/null +++ b/tests-clar/resources/testrepo.git/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f @@ -0,0 +1,2 @@ +x;j1Dmdǎ|M3`V{ >QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S + U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests-clar/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 new file mode 100644 index 000000000..29c8e824d --- /dev/null +++ b/tests-clar/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 @@ -0,0 +1,3 @@ +xQ +!@sBQ" ٱ r{ +F- \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a new file mode 100644 index 000000000..2ef4faa0f Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests-clar/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af new file mode 100644 index 000000000..716b0c64b --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af @@ -0,0 +1 @@ +xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR, \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests-clar/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d new file mode 100644 index 000000000..2f9b6b6e3 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 new file mode 100644 index 000000000..5df58dda5 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests-clar/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 new file mode 100644 index 000000000..4cc3f4dff --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 @@ -0,0 +1 @@ +x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1X \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests-clar/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 new file mode 100644 index 000000000..bf7b2bb68 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a new file mode 100644 index 000000000..a79612435 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a @@ -0,0 +1,3 @@ +x[ +0E*fդ "W0-Ft݁pS[Yx^ +Db CLhut}8X*4ZsYUA X3RM) s6輢Mរ&Jm;}<\@ޏpĀv?jۺL?H \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f new file mode 100644 index 000000000..f8588696b --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f @@ -0,0 +1,2 @@ +x;j1Dmdǎ|M3`V{ >QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S + U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests-clar/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 new file mode 100644 index 000000000..29c8e824d --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 @@ -0,0 +1,3 @@ +xQ +!@sBQ" ٱ r{ 13 */ + cl_assert(i == 13); +} + +void test_revwalk_basic__push_head(void) +{ + int i = 0; + git_oid oid; + + cl_git_pass(git_revwalk_push_head(_walk)); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline | wc -l => 7 */ + cl_assert(i == 7); +} + +void test_revwalk_basic__push_head_hide_ref(void) +{ + int i = 0; + git_oid oid; + + cl_git_pass(git_revwalk_push_head(_walk)); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline --not refs/heads/packed-test | wc -l => 4 */ + cl_assert(i == 4); +} + +void test_revwalk_basic__push_head_hide_ref_nobase(void) +{ + int i = 0; + git_oid oid; + + cl_git_pass(git_revwalk_push_head(_walk)); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline --not refs/heads/packed | wc -l => 7 */ + cl_assert(i == 7); +} diff --git a/tests-clar/revwalk/mergebase.c b/tests-clar/revwalk/mergebase.c new file mode 100644 index 000000000..e807e3ad2 --- /dev/null +++ b/tests-clar/revwalk/mergebase.c @@ -0,0 +1,148 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; + +void test_revwalk_mergebase__initialize(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); +} + +void test_revwalk_mergebase__cleanup(void) +{ + git_repository_free(_repo); +} + +void test_revwalk_mergebase__single1(void) +{ + git_oid result, one, two, expected; + + git_oid_fromstr(&one, "c47800c7266a2be04c571c04d5a6614691ea99bd "); + git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + git_oid_fromstr(&expected, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert(git_oid_cmp(&result, &expected) == 0); +} + +void test_revwalk_mergebase__single2(void) +{ + git_oid result, one, two, expected; + + git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af"); + git_oid_fromstr(&two, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + 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__merged_branch(void) +{ + git_oid result, one, two, expected; + + git_oid_fromstr(&one, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + git_oid_fromstr(&expected, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert(git_oid_cmp(&result, &expected) == 0); + + cl_git_pass(git_merge_base(&result, _repo, &two, &one)); + cl_assert(git_oid_cmp(&result, &expected) == 0); +} + +void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) +{ + git_oid result, one, two, expected; + int error; + + git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af"); + git_oid_fromstr(&two, "e90810b8df3e80c413d903f631643c716887138d"); + git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd"); + + error = git_merge_base(&result, _repo, &one, &two); + cl_git_fail(error); + + cl_assert_equal_i(GIT_ENOTFOUND, error); +} + +/* + * $ git log --graph --all + * * commit 763d71aadf09a7951596c9746c024e7eece7c7af + * | Author: nulltoken + * | Date: Sun Oct 9 12:54:47 2011 +0200 + * | + * | Add some files into subdirectories + * | + * | * commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * | | Author: Scott Chacon + * | | Date: Tue Aug 9 19:33:46 2011 -0700 + * | | + * | * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + * | |\ Merge: 9fd738e c47800c + * | |/ Author: Scott Chacon + * |/| Date: Tue May 25 11:58:27 2010 -0700 + * | | + * | | Merge branch 'br2' + * | | + * | | * commit e90810b8df3e80c413d903f631643c716887138d + * | | | Author: Vicent Marti + * | | | Date: Thu Aug 5 18:42:20 2010 +0200 + * | | | + * | | | Test commit 2 + * | | | + * | | * commit 6dcf9bf7541ee10456529833502442f385010c3d + * | | Author: Vicent Marti + * | | Date: Thu Aug 5 18:41:33 2010 +0200 + * | | + * | | Test commit 1 + * | | + * | | * commit a4a7dce85cf63874e984719f4fdd239f5145052f + * | | |\ Merge: c47800c 9fd738e + * | |/ / Author: Scott Chacon + * |/| / Date: Tue May 25 12:00:23 2010 -0700 + * | |/ + * | | Merge branch 'master' into br2 + * | | + * | * commit 9fd738e8f7967c078dceed8190330fc8648ee56a + * | | Author: Scott Chacon + * | | Date: Mon May 24 10:19:19 2010 -0700 + * | | + * | | a fourth commit + * | | + * | * commit 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * | | Author: Scott Chacon + * | | Date: Mon May 24 10:19:04 2010 -0700 + * | | + * | | a third commit + * | | + * * | commit c47800c7266a2be04c571c04d5a6614691ea99bd + * |/ Author: Scott Chacon + * | Date: Tue May 25 11:58:14 2010 -0700 + * | + * | branch commit one + * | + * * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 + * | Author: Scott Chacon + * | Date: Tue May 11 13:38:42 2010 -0700 + * | + * | another commit + * | + * * commit 8496071c1b46c854b31185ea97743be6a8774479 + * Author: Scott Chacon + * Date: Sat May 8 16:13:06 2010 -0700 + * + * testing + * + * * commit 41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 + * | Author: Scott Chacon + * | Date: Tue May 11 13:40:41 2010 -0700 + * | + * | packed commit two + * | + * * commit 5001298e0c09ad9c34e4249bc5801c75e9754fa5 + * Author: Scott Chacon + * Date: Tue May 11 13:40:23 2010 -0700 + * + * packed commit one + */ diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c new file mode 100644 index 000000000..369b25bda --- /dev/null +++ b/tests-clar/status/ignore.c @@ -0,0 +1,133 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "git2/attr.h" +#include "attr.h" +#include "status_helpers.h" + +static git_repository *g_repo = NULL; + +void test_status_ignore__initialize(void) +{ +} + +void test_status_ignore__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_status_ignore__0(void) +{ + struct { + const char *path; + int expected; + } test_cases[] = { + /* patterns "sub" and "ign" from .gitignore */ + { "file", 0 }, + { "ign", 1 }, + { "sub", 1 }, + { "sub/file", 0 }, + { "sub/ign", 1 }, + { "sub/sub", 1 }, + { "sub/sub/file", 0 }, + { "sub/sub/ign", 1 }, + { "sub/sub/sub", 1 }, + /* pattern "dir/" from .gitignore */ + { "dir", 1 }, + { "dir/", 1 }, + { "sub/dir", 1 }, + { "sub/dir/", 1 }, + { "sub/sub/dir", 0 }, /* dir is not actually a dir, but a file */ + { NULL, 0 } + }, *one_test; + + g_repo = cl_git_sandbox_init("attr"); + + for (one_test = test_cases; one_test->path != NULL; one_test++) { + int ignored; + cl_git_pass(git_status_should_ignore(&ignored, g_repo, one_test->path)); + cl_assert_(ignored == one_test->expected, one_test->path); + } + + /* confirm that ignore files were cached */ + cl_assert(git_attr_cache__is_cached(g_repo, 0, ".git/info/exclude")); + cl_assert(git_attr_cache__is_cached(g_repo, 0, ".gitignore")); +} + + +void test_status_ignore__1(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n"); + git_attr_cache_flush(g_repo); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "root_test4.txt")); + cl_assert(ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/subdir_test2.txt")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "dir")); + cl_assert(ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "dir/")); + cl_assert(ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/dir")); + cl_assert(!ignored); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "sub/dir/")); + cl_assert(!ignored); +} + + +void test_status_ignore__empty_repo_with_gitignore_rewrite(void) +{ + status_entry_single st; + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/look-ma.txt", "I'm going to be ignored!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_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, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt")); + cl_assert(!ignored); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "*.nomatch\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert(st.count == 2); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt")); + cl_assert(!ignored); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "*.txt\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert(st.count == 2); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "look-ma.txt")); + cl_assert(ignored); +} + diff --git a/tests-clar/status/single.c b/tests-clar/status/single.c new file mode 100644 index 000000000..e900a31d6 --- /dev/null +++ b/tests-clar/status/single.c @@ -0,0 +1,29 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static void +cleanup__remove_file(void *_file) +{ + cl_must_pass(p_unlink((char *)_file)); +} + +/* test retrieving OID from a file apart from the ODB */ +void test_status_single__hash_single_file(void) +{ + static const char file_name[] = "new_file"; + static const char file_contents[] = "new_file\n"; + static const char file_hash[] = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a"; + + git_oid expected_id, actual_id; + + /* initialization */ + git_oid_fromstr(&expected_id, file_hash); + cl_git_mkfile(file_name, file_contents); + cl_set_cleanup(&cleanup__remove_file, (void *)file_name); + + cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJ_BLOB)); + cl_assert(git_oid_cmp(&expected_id, &actual_id) == 0); +} + + + diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h new file mode 100644 index 000000000..043b83009 --- /dev/null +++ b/tests-clar/status/status_data.h @@ -0,0 +1,202 @@ +#include "status_helpers.h" + +/* entries for a plain copy of tests/resources/status */ + +static const char *entry_paths0[] = { + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "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", + + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses0[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED, + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED, + GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED, + + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + + GIT_STATUS_WT_NEW, +}; + +static const size_t entry_count0 = 16; + +/* entries for a copy of tests/resources/status with all content + * deleted from the working directory + */ + +static const char *entry_paths2[] = { + "current_file", + "file_deleted", + "modified_file", + "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", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const unsigned int entry_statuses2[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, +}; + +static const size_t entry_count2 = 15; + +/* entries for a copy of tests/resources/status with some mods */ + +static const char *entry_paths3[] = { + ".HEADER", + "42-is-not-prime.sigh", + "README.md", + "current_file", + "current_file/", + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "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", + "subdir", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses3[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, +}; + +static const size_t entry_count3 = 22; + + +/* entries for a copy of tests/resources/status with some mods + * and different options to the status call + */ + +static const char *entry_paths4[] = { + ".new_file", + "current_file", + "current_file/current_file", + "current_file/modified_file", + "current_file/new_file", + "file_deleted", + "modified_file", + "new_file", + "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", + "subdir", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + "zzz_new_dir/new_file", + "zzz_new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses4[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const size_t entry_count4 = 23; diff --git a/tests-clar/status/status_helpers.c b/tests-clar/status/status_helpers.c new file mode 100644 index 000000000..3dbf43a5b --- /dev/null +++ b/tests-clar/status/status_helpers.c @@ -0,0 +1,49 @@ +#include "clar_libgit2.h" +#include "status_helpers.h" + +int cb_status__normal( + const char *path, unsigned int status_flags, void *payload) +{ + status_entry_counts *counts = payload; + + if (counts->entry_count >= counts->expected_entry_count) { + counts->wrong_status_flags_count++; + goto exit; + } + + if (strcmp(path, counts->expected_paths[counts->entry_count])) { + counts->wrong_sorted_path++; + goto exit; + } + + if (status_flags != counts->expected_statuses[counts->entry_count]) + counts->wrong_status_flags_count++; + +exit: + counts->entry_count++; + return 0; +} + +int cb_status__count(const char *p, unsigned int s, void *payload) +{ + volatile int *count = (int *)payload; + + GIT_UNUSED(p); + GIT_UNUSED(s); + + (*count)++; + + return 0; +} + +int cb_status__single(const char *p, unsigned int s, void *payload) +{ + status_entry_single *data = (status_entry_single *)payload; + + GIT_UNUSED(p); + + data->count++; + data->status = s; + + return 0; +} diff --git a/tests-clar/status/status_helpers.h b/tests-clar/status/status_helpers.h new file mode 100644 index 000000000..cffca66a5 --- /dev/null +++ b/tests-clar/status/status_helpers.h @@ -0,0 +1,33 @@ +#ifndef INCLUDE_cl_status_helpers_h__ +#define INCLUDE_cl_status_helpers_h__ + +typedef struct { + size_t wrong_status_flags_count; + size_t wrong_sorted_path; + size_t entry_count; + const unsigned int* expected_statuses; + const char** expected_paths; + size_t expected_entry_count; +} status_entry_counts; + +/* cb_status__normal takes payload of "status_entry_counts *" */ + +extern int cb_status__normal( + const char *path, unsigned int status_flags, void *payload); + + +/* cb_status__count takes payload of "int *" */ + +extern int cb_status__count(const char *p, unsigned int s, void *payload); + + +typedef struct { + int count; + unsigned int status; +} status_entry_single; + +/* cb_status__single takes payload of "status_entry_single *" */ + +extern int cb_status__single(const char *p, unsigned int s, void *payload); + +#endif diff --git a/tests-clar/status/submodules.c b/tests-clar/status/submodules.c new file mode 100644 index 000000000..9423e8490 --- /dev/null +++ b/tests-clar/status/submodules.c @@ -0,0 +1,112 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "path.h" +#include "posix.h" +#include "status_helpers.h" + +static git_repository *g_repo = NULL; + +void test_status_submodules__initialize(void) +{ + git_buf modpath = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("submodules"); + + cl_fixture_sandbox("testrepo.git"); + + cl_git_pass(git_buf_sets(&modpath, git_repository_workdir(g_repo))); + cl_assert(git_path_dirname_r(&modpath, modpath.ptr) >= 0); + cl_git_pass(git_buf_joinpath(&modpath, modpath.ptr, "testrepo.git\n")); + + p_rename("submodules/gitmodules", "submodules/.gitmodules"); + cl_git_append2file("submodules/.gitmodules", modpath.ptr); + git_buf_free(&modpath); + + p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); +} + +void test_status_submodules__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_status_submodules__api(void) +{ + git_submodule *sm; + + cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND); + + cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + cl_assert(sm != NULL); + cl_assert_equal_s("testrepo", sm->name); + cl_assert_equal_s("testrepo", sm->path); +} + +void test_status_submodules__0(void) +{ + int counts = 0; + + cl_assert(git_path_isdir("submodules/.git")); + cl_assert(git_path_isdir("submodules/testrepo/.git")); + cl_assert(git_path_isfile("submodules/.gitmodules")); + + cl_git_pass( + git_status_foreach(g_repo, cb_status__count, &counts) + ); + + cl_assert(counts == 6); +} + +static const char *expected_files[] = { + ".gitmodules", + "added", + "deleted", + "ignored", + "modified", + "untracked" +}; + +static unsigned int expected_status[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW +}; + +static int +cb_status__match(const char *p, unsigned int s, void *payload) +{ + volatile int *index = (int *)payload; + + cl_assert_equal_s(expected_files[*index], p); + cl_assert(expected_status[*index] == s); + (*index)++; + + return 0; +} + +void test_status_submodules__1(void) +{ + int index = 0; + + cl_assert(git_path_isdir("submodules/.git")); + cl_assert(git_path_isdir("submodules/testrepo/.git")); + cl_assert(git_path_isfile("submodules/.gitmodules")); + + cl_git_pass( + git_status_foreach(g_repo, cb_status__match, &index) + ); + + cl_assert(index == 6); +} + +void test_status_submodules__single_file(void) +{ + unsigned int status; + cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); + cl_assert(status == 0); +} diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c new file mode 100644 index 000000000..b3ebdb781 --- /dev/null +++ b/tests-clar/status/worktree.c @@ -0,0 +1,583 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "ignore.h" +#include "status_data.h" +#include "posix.h" +#include "util.h" +#include "path.h" + +/** + * Initializer + * + * Not all of the tests in this file use the same fixtures, so we allow each + * test to load their fixture at the top of the test function. + */ +void test_status_worktree__initialize(void) +{ +} + +/** + * Cleanup + * + * This will be called once after each test finishes, even + * if the test failed + */ +void test_status_worktree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/** + * Tests - Status determination on a working tree + */ +/* this test is equivalent to t18-status.c:statuscb0 */ +void test_status_worktree__whole_repository(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count0; + counts.expected_paths = entry_paths0; + counts.expected_statuses = entry_statuses0; + + cl_git_pass( + git_status_foreach(repo, 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); +} + +/* this test is equivalent to t18-status.c:statuscb1 */ +void test_status_worktree__empty_repository(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + + cl_assert_equal_i(0, count); +} + +static int remove_file_cb(void *data, git_buf *file) +{ + const char *filename = git_buf_cstr(file); + + GIT_UNUSED(data); + + if (git__suffixcmp(filename, ".git") == 0) + return 0; + + if (git_path_isdir(filename)) + cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS)); + else + cl_git_pass(p_unlink(git_buf_cstr(file))); + + return 0; +} + +/* this test is equivalent to t18-status.c:statuscb2 */ +void test_status_worktree__purged_worktree(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_buf workdir = GIT_BUF_INIT; + + /* 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)); + git_buf_free(&workdir); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count2; + counts.expected_paths = entry_paths2; + counts.expected_statuses = entry_statuses2; + + cl_git_pass( + git_status_foreach(repo, 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); +} + +/* this test is similar to t18-status.c:statuscb3 */ +void test_status_worktree__swap_subdir_and_file(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts; + + /* first alter the contents of the worktree */ + cl_git_pass(p_rename("status/current_file", "status/swap")); + cl_git_pass(p_rename("status/subdir", "status/current_file")); + cl_git_pass(p_rename("status/swap", "status/subdir")); + + cl_git_mkfile("status/.HEADER", "dummy"); + cl_git_mkfile("status/42-is-not-prime.sigh", "dummy"); + cl_git_mkfile("status/README.md", "dummy"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count3; + counts.expected_paths = entry_paths3; + counts.expected_statuses = entry_statuses3; + + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED; + + 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__swap_subdir_with_recurse_and_pathspec(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts; + + /* first alter the contents of the worktree */ + cl_git_pass(p_rename("status/current_file", "status/swap")); + cl_git_pass(p_rename("status/subdir", "status/current_file")); + cl_git_pass(p_rename("status/swap", "status/subdir")); + cl_git_mkfile("status/.new_file", "dummy"); + cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777)); + cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); + cl_git_mkfile("status/zzz_new_file", "dummy"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count4; + counts.expected_paths = entry_paths4; + counts.expected_statuses = entry_statuses4; + + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + /* TODO: set pathspec to "current_file" eventually */ + + 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); +} + +/* this test is equivalent to t18-status.c:singlestatus0 */ +void test_status_worktree__single_file(void) +{ + int i; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass( + git_status_file(&status_flags, repo, entry_paths0[i]) + ); + cl_assert(entry_statuses0[i] == status_flags); + } +} + +/* this test is equivalent to t18-status.c:singlestatus1 */ +void test_status_worktree__single_nonexistent_file(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + error = git_status_file(&status_flags, repo, "nonexistent"); + cl_git_fail(error); + cl_assert(error == GIT_ENOTFOUND); +} + +/* this test is equivalent to t18-status.c:singlestatus2 */ +void test_status_worktree__single_nonexistent_file_empty_repo(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + error = git_status_file(&status_flags, repo, "nonexistent"); + cl_git_fail(error); + cl_assert(error == GIT_ENOTFOUND); +} + +/* this test is equivalent to t18-status.c:singlestatus3 */ +void test_status_worktree__single_file_empty_repo(void) +{ + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/new_file", "new_file\n"); + + cl_git_pass(git_status_file(&status_flags, repo, "new_file")); + cl_assert(status_flags == GIT_STATUS_WT_NEW); +} + +/* this test is equivalent to t18-status.c:singlestatus4 */ +void test_status_worktree__single_folder(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + error = git_status_file(&status_flags, repo, "subdir"); + cl_git_fail(error); + cl_assert(error != GIT_ENOTFOUND); +} + + +void test_status_worktree__ignores(void) +{ + int i, ignored; + git_repository *repo = cl_git_sandbox_init("status"); + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass( + git_status_should_ignore(&ignored, repo, entry_paths0[i]) + ); + cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); + } + + cl_git_pass( + git_status_should_ignore(&ignored, repo, "nonexistent_file") + ); + cl_assert(!ignored); + + cl_git_pass( + git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file") + ); + cl_assert(ignored); +} + +static int cb_status__check_592(const char *p, unsigned int s, void *payload) +{ + GIT_UNUSED(payload); + + if (s != GIT_STATUS_WT_DELETED || (payload != NULL && strcmp(p, (const char *)payload) != 0)) + return -1; + + return 0; +} + +void test_status_worktree__issue_592(void) +{ + git_repository *repo; + git_buf path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init("issue_592"); + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "l.txt")); + cl_git_pass(p_unlink(git_buf_cstr(&path))); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt")); + + git_buf_free(&path); +} + +void test_status_worktree__issue_592_2(void) +{ + git_repository *repo; + git_buf path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init("issue_592"); + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c/a.txt")); + cl_git_pass(p_unlink(git_buf_cstr(&path))); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); + + git_buf_free(&path); +} + +void test_status_worktree__issue_592_3(void) +{ + git_repository *repo; + git_buf path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); + + git_buf_free(&path); +} + +void test_status_worktree__issue_592_4(void) +{ + git_repository *repo; + git_buf path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t/b.txt")); + cl_git_pass(p_unlink(git_buf_cstr(&path))); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt")); + + git_buf_free(&path); +} + +void test_status_worktree__issue_592_5(void) +{ + git_repository *repo; + git_buf path = GIT_BUF_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777)); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL)); + + git_buf_free(&path); +} + +void test_status_worktree__issue_592_ignores_0(void) +{ + int count = 0; + status_entry_single st; + git_repository *repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(0, count); + + cl_git_rewritefile("issue_592/.gitignore", + ".gitignore\n*.txt\nc/\n[tT]*/\n"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(1, count); + + /* This is a situation where the behavior of libgit2 is + * different from core git. Core git will show ignored.txt + * in the list of ignored files, even though the directory + * "t" is ignored and the file is untracked because we have + * the explicit "*.txt" ignore rule. Libgit2 just excludes + * all untracked files that are contained within ignored + * directories without explicitly listing them. + */ + cl_git_rewritefile("issue_592/t/ignored.txt", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); +} + +void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("issue_592b"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(1, count); + + /* if we are really mimicking core git, then only ignored1.txt + * at the top level will show up in the ignores list here. + * everything else will be unmodified or skipped completely. + */ +} + +void test_status_worktree__cannot_retrieve_the_status_of_a_bare_repository(void) +{ + git_repository *repo; + int error; + unsigned int status = 0; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + error = git_status_file(&status, repo, "dummy"); + + cl_git_fail(error); + cl_assert(error != GIT_ENOTFOUND); + + git_repository_free(repo); +} + +void test_status_worktree__first_commit_in_progress(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + cl_git_mkfile("getting_started/testfile.txt", "content\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + + + +void test_status_worktree__status_file_without_index_or_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd")); + + cl_git_pass(git_index_open(&index, "empty-index")); + cl_assert_equal_i(0, git_index_entrycount(index)); + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); +} + +static void fill_index_wth_head_entries(git_repository *repo, git_index *index) +{ + git_oid oid; + git_commit *commit; + git_tree *tree; + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_write(index)); + + git_tree_free(tree); + git_commit_free(commit); +} + +void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd")); + + cl_git_pass(git_index_open(&index, "my-index")); + fill_index_wth_head_entries(repo, index); + + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_WT_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); + cl_git_pass(p_unlink("my-index")); +} + + +void test_status_worktree__space_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + +#define FILE_WITH_SPACE "LICENSE - copy.md" + + cl_git_pass(git_repository_init(&repo, "with_space", 0)); + cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, FILE_WITH_SPACE, 0)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} diff --git a/tests-clar/threads/basic.c b/tests-clar/threads/basic.c new file mode 100644 index 000000000..2b1c36808 --- /dev/null +++ b/tests-clar/threads/basic.c @@ -0,0 +1,20 @@ +#include "clar_libgit2.h" + +#include "cache.h" + + +static git_repository *g_repo; + +void test_threads_basic__initialize(void) { + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_threads_basic__cleanup(void) { + cl_git_sandbox_cleanup(); +} + + +void test_threads_basic__cache(void) { + // run several threads polling the cache at the same time + cl_assert(1 == 1); +} diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 690624bdf..000000000 --- a/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.toc diff --git a/tests/NAMING b/tests/NAMING deleted file mode 100644 index df07b7d51..000000000 --- a/tests/NAMING +++ /dev/null @@ -1,46 +0,0 @@ -Test sources should be named: - - t????-function.c - -where ???? is a four digit code. The first two digits classify -the test into a major category; the final two digits indicate the -sequence of the test within that category. The function part of -the test name should give a rough indication of what it does. - -Categories ----------- - -00__: Core library routines based only on the standard library, - and that are essential for everything else to run. E.g. - errno and malloc. - -01__: Basic hashing functions, needed to handle the content - addressable store. - -02__: Basic object read access. - -03__: Basic object writing. - -04__: Parsing and loading commit data - -05__: Revision walking - -06__: Index reading, writing and searching - -07__: Tests for the internal hashtable code - -08__: Tag reading and writing - -09__: Reading tree objects - -10__: Symbolic, loose and packed references reading and writing. - -11__: SQLite backend - -12__: Repository init and opening - -13__: Threads, empty as of now - -14__: Redis backend - -15__: Configuration parsing diff --git a/tests/resources/testrepo.git/refs/heads/master b/tests/resources/testrepo.git/refs/heads/master deleted file mode 100644 index 9536ad89c..000000000 Binary files a/tests/resources/testrepo.git/refs/heads/master and /dev/null differ diff --git a/tests/t00-core.c b/tests/t00-core.c deleted file mode 100644 index ab9a683a7..000000000 --- a/tests/t00-core.c +++ /dev/null @@ -1,686 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" - -#include "vector.h" -#include "fileops.h" - -BEGIN_TEST(string0, "compare prefixes") - must_be_true(git__prefixcmp("", "") == 0); - must_be_true(git__prefixcmp("a", "") == 0); - must_be_true(git__prefixcmp("", "a") < 0); - must_be_true(git__prefixcmp("a", "b") < 0); - must_be_true(git__prefixcmp("b", "a") > 0); - must_be_true(git__prefixcmp("ab", "a") == 0); - must_be_true(git__prefixcmp("ab", "ac") < 0); - must_be_true(git__prefixcmp("ab", "aa") > 0); -END_TEST - -BEGIN_TEST(string1, "compare suffixes") - must_be_true(git__suffixcmp("", "") == 0); - must_be_true(git__suffixcmp("a", "") == 0); - must_be_true(git__suffixcmp("", "a") < 0); - must_be_true(git__suffixcmp("a", "b") < 0); - must_be_true(git__suffixcmp("b", "a") > 0); - must_be_true(git__suffixcmp("ba", "a") == 0); - must_be_true(git__suffixcmp("zaa", "ac") < 0); - must_be_true(git__suffixcmp("zaz", "ac") > 0); -END_TEST - - -BEGIN_TEST(vector0, "initial size of 1 would cause writing past array bounds") - git_vector x; - int i; - git_vector_init(&x, 1, NULL); - for (i = 0; i < 10; ++i) { - git_vector_insert(&x, (void*) 0xabc); - } - git_vector_free(&x); -END_TEST - -BEGIN_TEST(vector1, "don't read past array bounds on remove()") - git_vector x; - // make initial capacity exact for our insertions. - git_vector_init(&x, 3, NULL); - git_vector_insert(&x, (void*) 0xabc); - git_vector_insert(&x, (void*) 0xdef); - git_vector_insert(&x, (void*) 0x123); - - git_vector_remove(&x, 0); // used to read past array bounds. - git_vector_free(&x); -END_TEST - - -BEGIN_TEST(path0, "get the dirname of a path") - char dir[64], *dir2; - -#define DIRNAME_TEST(A, B) { \ - must_be_true(git__dirname_r(dir, sizeof(dir), A) >= 0); \ - must_be_true(strcmp(dir, B) == 0); \ - must_be_true((dir2 = git__dirname(A)) != NULL); \ - must_be_true(strcmp(dir2, B) == 0); \ - free(dir2); \ -} - - DIRNAME_TEST(NULL, "."); - DIRNAME_TEST("", "."); - DIRNAME_TEST("a", "."); - DIRNAME_TEST("/", "/"); - DIRNAME_TEST("/usr", "/"); - DIRNAME_TEST("/usr/", "/"); - DIRNAME_TEST("/usr/lib", "/usr"); - DIRNAME_TEST("/usr/lib/", "/usr"); - DIRNAME_TEST("/usr/lib//", "/usr"); - DIRNAME_TEST("usr/lib", "usr"); - DIRNAME_TEST("usr/lib/", "usr"); - DIRNAME_TEST("usr/lib//", "usr"); - DIRNAME_TEST(".git/", "."); - -#undef DIRNAME_TEST - -END_TEST - -BEGIN_TEST(path1, "get the base name of a path") - char base[64], *base2; - -#define BASENAME_TEST(A, B) { \ - must_be_true(git__basename_r(base, sizeof(base), A) >= 0); \ - must_be_true(strcmp(base, B) == 0); \ - must_be_true((base2 = git__basename(A)) != NULL); \ - must_be_true(strcmp(base2, B) == 0); \ - free(base2); \ -} - - BASENAME_TEST(NULL, "."); - BASENAME_TEST("", "."); - BASENAME_TEST("a", "a"); - BASENAME_TEST("/", "/"); - BASENAME_TEST("/usr", "usr"); - BASENAME_TEST("/usr/", "usr"); - BASENAME_TEST("/usr/lib", "lib"); - BASENAME_TEST("/usr/lib//", "lib"); - BASENAME_TEST("usr/lib", "lib"); - -#undef BASENAME_TEST - -END_TEST - -BEGIN_TEST(path2, "get the latest component in a path") - const char *dir; - -#define TOPDIR_TEST(A, B) { \ - must_be_true((dir = git__topdir(A)) != NULL); \ - must_be_true(strcmp(dir, B) == 0); \ -} - - TOPDIR_TEST(".git/", ".git/"); - TOPDIR_TEST("/.git/", ".git/"); - TOPDIR_TEST("usr/local/.git/", ".git/"); - TOPDIR_TEST("./.git/", ".git/"); - TOPDIR_TEST("/usr/.git/", ".git/"); - TOPDIR_TEST("/", "/"); - TOPDIR_TEST("a/", "a/"); - - must_be_true(git__topdir("/usr/.git") == NULL); - must_be_true(git__topdir(".") == NULL); - must_be_true(git__topdir("") == NULL); - must_be_true(git__topdir("a") == NULL); - -#undef TOPDIR_TEST -END_TEST - -typedef int (normalize_path)(char *, size_t, const char *); - -/* Assert flags */ -#define CWD_AS_PREFIX 1 -#define PATH_AS_SUFFIX 2 -#define ROOTED_PATH 4 - -static int ensure_normalized(const char *input_path, const char *expected_path, normalize_path normalizer, int assert_flags) -{ - int error = GIT_SUCCESS; - char buffer_out[GIT_PATH_MAX]; - char current_workdir[GIT_PATH_MAX]; - - error = gitfo_getcwd(current_workdir, sizeof(current_workdir)); - if (error < GIT_SUCCESS) - return error; - - error = normalizer(buffer_out, sizeof(buffer_out), input_path); - if (error < GIT_SUCCESS) - return error; - - if (expected_path == NULL) - return error; - - if ((assert_flags & PATH_AS_SUFFIX) != 0) - if (git__suffixcmp(buffer_out, expected_path)) - return GIT_ERROR; - - if ((assert_flags & CWD_AS_PREFIX) != 0) - if (git__prefixcmp(buffer_out, current_workdir)) - return GIT_ERROR; - - if ((assert_flags & ROOTED_PATH) != 0) { - error = strcmp(expected_path, buffer_out); - } - - return error; -} - -static int ensure_dir_path_normalized(const char *input_path, const char *expected_path, int assert_flags) -{ - return ensure_normalized(input_path, expected_path, gitfo_prettify_dir_path, assert_flags); -} - -static int ensure_file_path_normalized(const char *input_path, const char *expected_path, int assert_flags) -{ - return ensure_normalized(input_path, expected_path, gitfo_prettify_file_path, assert_flags); -} - -BEGIN_TEST(path3, "prettify and validate a path to a file") - must_pass(ensure_file_path_normalized("a", "a", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_file_path_normalized("./testrepo.git", "testrepo.git", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_file_path_normalized("./.git", ".git", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_file_path_normalized("./git.", "git.", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_fail(ensure_file_path_normalized("git./", NULL, 0)); - must_fail(ensure_file_path_normalized("", NULL, 0)); - must_fail(ensure_file_path_normalized(".", NULL, 0)); - must_fail(ensure_file_path_normalized("./", NULL, 0)); - must_fail(ensure_file_path_normalized("./.", NULL, 0)); - must_fail(ensure_file_path_normalized("./..", NULL, 0)); - must_fail(ensure_file_path_normalized("../.", NULL, 0)); - must_fail(ensure_file_path_normalized("./.././/", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/../..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/..///..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub///../..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub///..///..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/../../..", NULL, 0)); - must_pass(ensure_file_path_normalized("dir", "dir", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_fail(ensure_file_path_normalized("dir//", NULL, 0)); - must_pass(ensure_file_path_normalized("./dir", "dir", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_fail(ensure_file_path_normalized("dir/.", NULL, 0)); - must_fail(ensure_file_path_normalized("dir///./", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/..", NULL, 0)); - must_fail(ensure_file_path_normalized("dir//sub/..",NULL, 0)); - must_fail(ensure_file_path_normalized("dir//sub/../", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/../", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/sub/../.", NULL, 0)); - must_fail(ensure_file_path_normalized("dir/s1/../s2/", NULL, 0)); - must_fail(ensure_file_path_normalized("d1/s1///s2/..//../s3/", NULL, 0)); - must_pass(ensure_file_path_normalized("d1/s1//../s2/../../d2", "d2", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_fail(ensure_file_path_normalized("dir/sub/../", NULL, 0)); - must_pass(ensure_file_path_normalized("../a/../b/c/d/../../e", "b/e", PATH_AS_SUFFIX)); - must_fail(ensure_file_path_normalized("....", NULL, 0)); - must_fail(ensure_file_path_normalized("...", NULL, 0)); - must_fail(ensure_file_path_normalized("./...", NULL, 0)); - must_fail(ensure_file_path_normalized("d1/...", NULL, 0)); - must_fail(ensure_file_path_normalized("d1/.../", NULL, 0)); - must_fail(ensure_file_path_normalized("d1/.../d2", NULL, 0)); - - must_pass(ensure_file_path_normalized("/a", "/a", ROOTED_PATH)); - must_pass(ensure_file_path_normalized("/./testrepo.git", "/testrepo.git", ROOTED_PATH)); - must_pass(ensure_file_path_normalized("/./.git", "/.git", ROOTED_PATH)); - must_pass(ensure_file_path_normalized("/./git.", "/git.", ROOTED_PATH)); - must_fail(ensure_file_path_normalized("/git./", NULL, 0)); - must_fail(ensure_file_path_normalized("/", NULL, 0)); - must_fail(ensure_file_path_normalized("/.", NULL, 0)); - must_fail(ensure_file_path_normalized("/./", NULL, 0)); - must_fail(ensure_file_path_normalized("/./.", NULL, 0)); - must_fail(ensure_file_path_normalized("/./..", NULL, 0)); - must_fail(ensure_file_path_normalized("/../.", NULL, 0)); - must_fail(ensure_file_path_normalized("/./.././/", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/../..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/..///..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub///../..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub///..///..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/../../..", NULL, 0)); - must_pass(ensure_file_path_normalized("/dir", "/dir", 0)); - must_fail(ensure_file_path_normalized("/dir//", NULL, 0)); - must_pass(ensure_file_path_normalized("/./dir", "/dir", 0)); - must_fail(ensure_file_path_normalized("/dir/.", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir///./", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/..", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir//sub/..",NULL, 0)); - must_fail(ensure_file_path_normalized("/dir//sub/../", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/../", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/sub/../.", NULL, 0)); - must_fail(ensure_file_path_normalized("/dir/s1/../s2/", NULL, 0)); - must_fail(ensure_file_path_normalized("/d1/s1///s2/..//../s3/", NULL, 0)); - must_pass(ensure_file_path_normalized("/d1/s1//../s2/../../d2", "/d2", 0)); - must_fail(ensure_file_path_normalized("/dir/sub/../", NULL, 0)); - must_fail(ensure_file_path_normalized("/....", NULL, 0)); - must_fail(ensure_file_path_normalized("/...", NULL, 0)); - must_fail(ensure_file_path_normalized("/./...", NULL, 0)); - must_fail(ensure_file_path_normalized("/d1/...", NULL, 0)); - must_fail(ensure_file_path_normalized("/d1/.../", NULL, 0)); - must_fail(ensure_file_path_normalized("/d1/.../d2", NULL, 0)); -END_TEST - -BEGIN_TEST(path4, "validate and prettify a path to a folder") - must_pass(ensure_dir_path_normalized("./testrepo.git", "testrepo.git/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("./.git", ".git/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("./git.", "git./", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("git./", "git./", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized(".", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("./", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("./.", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/..", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/../..", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/..///..", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub///../..", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub///..///..", "", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir//", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("./dir", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/.", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir///./", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/..", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir//sub/..", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir//sub/../", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/../", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/../.", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/s1/../s2/", "dir/s2/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("d1/s1///s2/..//../s3/", "d1/s3/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("d1/s1//../s2/../../d2", "d2/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("dir/sub/../", "dir/", CWD_AS_PREFIX | PATH_AS_SUFFIX)); - must_pass(ensure_dir_path_normalized("../a/../b/c/d/../../e", "b/e/", PATH_AS_SUFFIX)); - must_fail(ensure_dir_path_normalized("....", NULL, 0)); - must_fail(ensure_dir_path_normalized("...", NULL, 0)); - must_fail(ensure_dir_path_normalized("./...", NULL, 0)); - must_fail(ensure_dir_path_normalized("d1/...", NULL, 0)); - must_fail(ensure_dir_path_normalized("d1/.../", NULL, 0)); - must_fail(ensure_dir_path_normalized("d1/.../d2", NULL, 0)); - - must_pass(ensure_dir_path_normalized("/./testrepo.git", "/testrepo.git/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/./.git", "/.git/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/./git.", "/git./", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/git./", "/git./", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/", "/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("//", "/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("///", "/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/.", "/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/./", "/", ROOTED_PATH)); - must_fail(ensure_dir_path_normalized("/./..", NULL, 0)); - must_fail(ensure_dir_path_normalized("/../.", NULL, 0)); - must_fail(ensure_dir_path_normalized("/./.././/", NULL, 0)); - must_pass(ensure_dir_path_normalized("/dir/..", "/", 0)); - must_pass(ensure_dir_path_normalized("/dir/sub/../..", "/", 0)); - must_fail(ensure_dir_path_normalized("/dir/sub/../../..", NULL, 0)); - must_pass(ensure_dir_path_normalized("/dir", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir//", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/./dir", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir/.", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir///./", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir//sub/..", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir/sub/../", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("//dir/sub/../.", "/dir/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/dir/s1/../s2/", "/dir/s2/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/d1/s1///s2/..//../s3/", "/d1/s3/", ROOTED_PATH)); - must_pass(ensure_dir_path_normalized("/d1/s1//../s2/../../d2", "/d2/", ROOTED_PATH)); - must_fail(ensure_dir_path_normalized("/....", NULL, 0)); - must_fail(ensure_dir_path_normalized("/...", NULL, 0)); - must_fail(ensure_dir_path_normalized("/./...", NULL, 0)); - must_fail(ensure_dir_path_normalized("/d1/...", NULL, 0)); - must_fail(ensure_dir_path_normalized("/d1/.../", NULL, 0)); - must_fail(ensure_dir_path_normalized("/d1/.../d2", NULL, 0)); -END_TEST - -static int ensure_joinpath(const char *path_a, const char *path_b, const char *expected_path) -{ - char joined_path[GIT_PATH_MAX]; - git__joinpath(joined_path, path_a, path_b); - return strcmp(joined_path, expected_path) == 0 ? GIT_SUCCESS : GIT_ERROR; -} - -BEGIN_TEST(path5, "properly join path components") - must_pass(ensure_joinpath("", "", "")); - must_pass(ensure_joinpath("", "a", "a")); - must_pass(ensure_joinpath("", "/a", "/a")); - must_pass(ensure_joinpath("a", "", "a/")); - must_pass(ensure_joinpath("a", "/", "a/")); - must_pass(ensure_joinpath("a", "b", "a/b")); - must_pass(ensure_joinpath("/", "a", "/a")); - must_pass(ensure_joinpath("/", "", "/")); - must_pass(ensure_joinpath("/a", "/b", "/a/b")); - must_pass(ensure_joinpath("/a", "/b/", "/a/b/")); - must_pass(ensure_joinpath("/a/", "b/", "/a/b/")); - must_pass(ensure_joinpath("/a/", "/b/", "/a/b/")); -END_TEST - -static int ensure_joinpath_n(const char *path_a, const char *path_b, const char *path_c, const char *path_d, const char *expected_path) -{ - char joined_path[GIT_PATH_MAX]; - git__joinpath_n(joined_path, 4, path_a, path_b, path_c, path_d); - return strcmp(joined_path, expected_path) == 0 ? GIT_SUCCESS : GIT_ERROR; -} - -BEGIN_TEST(path6, "properly join path components for more than one path") - must_pass(ensure_joinpath_n("", "", "", "", "")); - must_pass(ensure_joinpath_n("", "a", "", "", "a/")); - must_pass(ensure_joinpath_n("a", "", "", "", "a/")); - must_pass(ensure_joinpath_n("", "", "", "a", "a")); - must_pass(ensure_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/")); - must_pass(ensure_joinpath_n("a", "b", "", "/c/d", "a/b/c/d")); -END_TEST - -static int count_number_of_path_segments(const char *path) -{ - int number = 0; - char *current = (char *)path; - - while (*current) - { - if (*current++ == '/') - number++; - } - - assert (number > 0); - - return --number; -} - -BEGIN_TEST(path7, "prevent a path which escapes the root directory from being prettified") - char current_workdir[GIT_PATH_MAX]; - char prettified[GIT_PATH_MAX]; - int i = 0, number_to_escape; - - must_pass(gitfo_getcwd(current_workdir, sizeof(current_workdir))); - - number_to_escape = count_number_of_path_segments(current_workdir); - - for (i = 0; i < number_to_escape + 1; i++) - git__joinpath(current_workdir, current_workdir, "../"); - - must_fail(gitfo_prettify_dir_path(prettified, sizeof(prettified), current_workdir)); -END_TEST - -typedef struct name_data { - int count; /* return count */ - char *name; /* filename */ -} name_data; - -typedef struct walk_data { - char *sub; /* sub-directory name */ - name_data *names; /* name state data */ -} walk_data; - - -static char path_buffer[GIT_PATH_MAX]; -static char *top_dir = "dir-walk"; -static walk_data *state_loc; - -static int error(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - return -1; -} - -static int setup(walk_data *d) -{ - name_data *n; - - if (gitfo_mkdir(top_dir, 0755) < 0) - return error("can't mkdir(\"%s\")", top_dir); - - if (gitfo_chdir(top_dir) < 0) - return error("can't chdir(\"%s\")", top_dir); - - if (strcmp(d->sub, ".") != 0) - if (gitfo_mkdir(d->sub, 0755) < 0) - return error("can't mkdir(\"%s\")", d->sub); - - strcpy(path_buffer, d->sub); - state_loc = d; - - for (n = d->names; n->name; n++) { - git_file fd = gitfo_creat(n->name, 0600); - if (fd < 0) - return GIT_ERROR; - gitfo_close(fd); - n->count = 0; - } - - return 0; -} - -static int knockdown(walk_data *d) -{ - name_data *n; - - for (n = d->names; n->name; n++) { - if (gitfo_unlink(n->name) < 0) - return error("can't unlink(\"%s\")", n->name); - } - - if (strcmp(d->sub, ".") != 0) - if (gitfo_rmdir(d->sub) < 0) - return error("can't rmdir(\"%s\")", d->sub); - - if (gitfo_chdir("..") < 0) - return error("can't chdir(\"..\")"); - - if (gitfo_rmdir(top_dir) < 0) - return error("can't rmdir(\"%s\")", top_dir); - - return 0; -} - -static int check_counts(walk_data *d) -{ - int ret = 0; - name_data *n; - - for (n = d->names; n->name; n++) { - if (n->count != 1) - ret = error("count (%d, %s)", n->count, n->name); - } - return ret; -} - -static int one_entry(void *state, char *path) -{ - walk_data *d = (walk_data *) state; - name_data *n; - - if (state != state_loc) - return GIT_ERROR; - - if (path != path_buffer) - return GIT_ERROR; - - for (n = d->names; n->name; n++) { - if (!strcmp(n->name, path)) { - n->count++; - return 0; - } - } - - return GIT_ERROR; -} - - -static name_data dot_names[] = { - { 0, "./a" }, - { 0, "./asdf" }, - { 0, "./pack-foo.pack" }, - { 0, NULL } -}; -static walk_data dot = { - ".", - dot_names -}; - -BEGIN_TEST(dirent0, "make sure that the '.' folder is not traversed") - - must_pass(setup(&dot)); - - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - one_entry, - &dot)); - - must_pass(check_counts(&dot)); - - must_pass(knockdown(&dot)); -END_TEST - -static name_data sub_names[] = { - { 0, "sub/a" }, - { 0, "sub/asdf" }, - { 0, "sub/pack-foo.pack" }, - { 0, NULL } -}; -static walk_data sub = { - "sub", - sub_names -}; - -BEGIN_TEST(dirent1, "traverse a subfolder") - - must_pass(setup(&sub)); - - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - one_entry, - &sub)); - - must_pass(check_counts(&sub)); - - must_pass(knockdown(&sub)); -END_TEST - -static walk_data sub_slash = { - "sub/", - sub_names -}; - -BEGIN_TEST(dirent2, "traverse a slash-terminated subfolder") - - must_pass(setup(&sub_slash)); - - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - one_entry, - &sub_slash)); - - must_pass(check_counts(&sub_slash)); - - must_pass(knockdown(&sub_slash)); -END_TEST - -static name_data empty_names[] = { - { 0, NULL } -}; -static walk_data empty = { - "empty", - empty_names -}; - -static int dont_call_me(void *GIT_UNUSED(state), char *GIT_UNUSED(path)) -{ - GIT_UNUSED_ARG(state) - GIT_UNUSED_ARG(path) - return GIT_ERROR; -} - -BEGIN_TEST(dirent3, "make sure that empty folders are not traversed") - - must_pass(setup(&empty)); - - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - one_entry, - &empty)); - - must_pass(check_counts(&empty)); - - /* make sure callback not called */ - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - dont_call_me, - &empty)); - - must_pass(knockdown(&empty)); -END_TEST - -static name_data odd_names[] = { - { 0, "odd/.a" }, - { 0, "odd/..c" }, - /* the following don't work on cygwin/win32 */ - /* { 0, "odd/.b." }, */ - /* { 0, "odd/..d.." }, */ - { 0, NULL } -}; -static walk_data odd = { - "odd", - odd_names -}; - -BEGIN_TEST(dirent4, "make sure that strange looking filenames ('..c') are traversed") - - must_pass(setup(&odd)); - - must_pass(gitfo_dirent(path_buffer, - sizeof(path_buffer), - one_entry, - &odd)); - - must_pass(check_counts(&odd)); - - must_pass(knockdown(&odd)); -END_TEST - - -BEGIN_SUITE(core) - ADD_TEST(string0); - ADD_TEST(string1); - - ADD_TEST(vector0); - ADD_TEST(vector1); - - ADD_TEST(path0); - ADD_TEST(path1); - ADD_TEST(path2); - ADD_TEST(path3); - ADD_TEST(path4); - ADD_TEST(path5); - ADD_TEST(path6); - ADD_TEST(path7); - - ADD_TEST(dirent0); - ADD_TEST(dirent1); - ADD_TEST(dirent2); - ADD_TEST(dirent3); - ADD_TEST(dirent4); -END_SUITE diff --git a/tests/t01-rawobj.c b/tests/t01-rawobj.c deleted file mode 100644 index 5db9a79fc..000000000 --- a/tests/t01-rawobj.c +++ /dev/null @@ -1,638 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" - -#include "odb.h" -#include "hash.h" - -#include "t01-data.h" - -static int hash_object(git_oid *oid, git_rawobj *obj) -{ - return git_odb_hash(oid, obj->data, obj->len, obj->type); -} - -BEGIN_TEST(oid0, "validate size of oid objects") - git_oid out; - must_be_true(20 == GIT_OID_RAWSZ); - must_be_true(40 == GIT_OID_HEXSZ); - must_be_true(sizeof(out) == GIT_OID_RAWSZ); - must_be_true(sizeof(out.id) == GIT_OID_RAWSZ); -END_TEST - -BEGIN_TEST(oid1, "fail when parsing an empty string as oid") - git_oid out; - must_fail(git_oid_mkstr(&out, "")); -END_TEST - -BEGIN_TEST(oid2, "fail when parsing an invalid string as oid") - git_oid out; - must_fail(git_oid_mkstr(&out, "moo")); -END_TEST - -static int from_hex(unsigned int i) -{ - if (i >= '0' && i <= '9') - return i - '0'; - if (i >= 'a' && i <= 'f') - return 10 + (i - 'a'); - if (i >= 'A' && i <= 'F') - return 10 + (i - 'A'); - return -1; -} - -BEGIN_TEST(oid3, "find all invalid characters when parsing an oid") - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - char in[41] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0"; - unsigned int i; - - for (i = 0; i < 256; i++) { - in[38] = (char)i; - - if (from_hex(i) >= 0) { - exp[19] = (unsigned char)(from_hex(i) << 4); - must_pass(git_oid_mkstr(&out, in)); - must_be_true(memcmp(out.id, exp, sizeof(out.id)) == 0); - } else { - must_fail(git_oid_mkstr(&out, in)); - } - } -END_TEST - -BEGIN_TEST(oid4, "fail when parsing an invalid oid string") - git_oid out; - must_fail(git_oid_mkstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez")); -END_TEST - -BEGIN_TEST(oid5, "succeed when parsing a valid oid string") - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - - must_pass(git_oid_mkstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0")); - must_pass(memcmp(out.id, exp, sizeof(out.id))); - - must_pass(git_oid_mkstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0")); - must_pass(memcmp(out.id, exp, sizeof(out.id))); -END_TEST - -BEGIN_TEST(oid6, "build a valid oid from raw bytes") - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - - git_oid_mkraw(&out, exp); - must_pass(memcmp(out.id, exp, sizeof(out.id))); -END_TEST - -BEGIN_TEST(oid7, "properly copy an oid to another") - git_oid a, b; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - - memset(&b, 0, sizeof(b)); - git_oid_mkraw(&a, exp); - git_oid_cpy(&b, &a); - must_pass(memcmp(a.id, exp, sizeof(a.id))); -END_TEST - -BEGIN_TEST(oid8, "compare two oids (lesser than)") - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - unsigned char b_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xf0, - }; - - git_oid_mkraw(&a, a_in); - git_oid_mkraw(&b, b_in); - must_be_true(git_oid_cmp(&a, &b) < 0); -END_TEST - -BEGIN_TEST(oid9, "compare two oids (equal)") - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - - git_oid_mkraw(&a, a_in); - git_oid_mkraw(&b, a_in); - must_be_true(git_oid_cmp(&a, &b) == 0); -END_TEST - -BEGIN_TEST(oid10, "compare two oids (greater than)") - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - unsigned char b_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xd0, - }; - - git_oid_mkraw(&a, a_in); - git_oid_mkraw(&b, b_in); - must_be_true(git_oid_cmp(&a, &b) > 0); -END_TEST - -BEGIN_TEST(oid11, "compare formated oids") - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 1]; - - must_pass(git_oid_mkstr(&in, exp)); - - /* Format doesn't touch the last byte */ - out[GIT_OID_HEXSZ] = 'Z'; - git_oid_fmt(out, &in); - must_be_true(out[GIT_OID_HEXSZ] == 'Z'); - - /* Format produced the right result */ - out[GIT_OID_HEXSZ] = '\0'; - must_pass(strcmp(exp, out)); -END_TEST - -BEGIN_TEST(oid12, "compare oids (allocate + format)") - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char *out; - - must_pass(git_oid_mkstr(&in, exp)); - - out = git_oid_allocfmt(&in); - must_be_true(out); - must_pass(strcmp(exp, out)); - free(out); -END_TEST - -BEGIN_TEST(oid13, "compare oids (path format)") - const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 2]; - - must_pass(git_oid_mkstr(&in, exp1)); - - /* Format doesn't touch the last byte */ - out[GIT_OID_HEXSZ + 1] = 'Z'; - git_oid_pathfmt(out, &in); - must_be_true(out[GIT_OID_HEXSZ + 1] == 'Z'); - - /* Format produced the right result */ - out[GIT_OID_HEXSZ + 1] = '\0'; - must_pass(strcmp(exp2, out)); -END_TEST - -BEGIN_TEST(oid14, "convert raw oid to string") - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 1]; - char *str; - int i; - - must_pass(git_oid_mkstr(&in, exp)); - - /* NULL buffer pointer, returns static empty string */ - str = git_oid_to_string(NULL, sizeof(out), &in); - must_be_true(str && *str == '\0' && str != out); - - /* zero buffer size, returns static empty string */ - str = git_oid_to_string(out, 0, &in); - must_be_true(str && *str == '\0' && str != out); - - /* NULL oid pointer, returns static empty string */ - str = git_oid_to_string(out, sizeof(out), NULL); - must_be_true(str && *str == '\0' && str != out); - - /* n == 1, returns out as an empty string */ - str = git_oid_to_string(out, 1, &in); - must_be_true(str && *str == '\0' && str == out); - - for (i = 1; i < GIT_OID_HEXSZ; i++) { - out[i+1] = 'Z'; - str = git_oid_to_string(out, i+1, &in); - /* returns out containing c-string */ - must_be_true(str && str == out); - /* must be '\0' terminated */ - must_be_true(*(str+i) == '\0'); - /* must not touch bytes past end of string */ - must_be_true(*(str+(i+1)) == 'Z'); - /* i == n-1 charaters of string */ - must_pass(strncmp(exp, out, i)); - } - - /* returns out as hex formatted c-string */ - str = git_oid_to_string(out, sizeof(out), &in); - must_be_true(str && str == out && *(str+GIT_OID_HEXSZ) == '\0'); - must_pass(strcmp(exp, out)); -END_TEST - -BEGIN_TEST(oid15, "convert raw oid to string (big)") - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ - char *str; - - must_pass(git_oid_mkstr(&in, exp)); - - /* place some tail material */ - big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */ - big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */ - big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */ - big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */ - - /* returns big as hex formatted c-string */ - str = git_oid_to_string(big, sizeof(big), &in); - must_be_true(str && str == big && *(str+GIT_OID_HEXSZ) == '\0'); - must_pass(strcmp(exp, big)); - - /* check tail material is untouched */ - must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X'); - must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y'); - must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); -END_TEST - - -BEGIN_TEST(oid16, "make sure the OID shortener doesn't choke on duplicate sha1s") - - git_oid_shorten *os; - int min_len; - - os = git_oid_shorten_new(0); - must_be_true(os != NULL); - - git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511"); - git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0"); - min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - - must_be_true(min_len == GIT_OID_HEXSZ + 1); - - git_oid_shorten_free(os); -END_TEST - -BEGIN_TEST(oid17, "stress test for the git_oid_shorten object") - -#define MAX_OIDS 1000 - - git_oid_shorten *os; - char *oids[MAX_OIDS]; - char number_buffer[16]; - git_oid oid; - size_t i, j; - - int min_len = 0, found_collision; - - os = git_oid_shorten_new(0); - must_be_true(os != NULL); - - /* - * Insert in the shortener 1000 unique SHA1 ids - */ - for (i = 0; i < MAX_OIDS; ++i) { - char *oid_text; - - sprintf(number_buffer, "%u", (unsigned int)i); - git_hash_buf(&oid, number_buffer, strlen(number_buffer)); - - oid_text = git__malloc(GIT_OID_HEXSZ + 1); - git_oid_fmt(oid_text, &oid); - oid_text[GIT_OID_HEXSZ] = 0; - - min_len = git_oid_shorten_add(os, oid_text); - must_be_true(min_len >= 0); - - oids[i] = oid_text; - } - - /* - * Compare the first `min_char - 1` characters of each - * SHA1 OID. If the minimizer worked, we should find at - * least one collision - */ - found_collision = 0; - for (i = 0; i < MAX_OIDS; ++i) { - for (j = 0; j < MAX_OIDS; ++j) { - if (i != j && memcmp(oids[i], oids[j], min_len - 1) == 0) - found_collision = 1; - } - } - must_be_true(found_collision == 1); - - /* - * Compare the first `min_char` characters of each - * SHA1 OID. If the minimizer worked, every single preffix - * should be unique. - */ - found_collision = 0; - for (i = 0; i < MAX_OIDS; ++i) { - for (j = 0; j < MAX_OIDS; ++j) { - if (i != j && memcmp(oids[i], oids[j], min_len) == 0) - found_collision = 1; - } - } - must_be_true(found_collision == 0); - - /* cleanup */ - for (i = 0; i < MAX_OIDS; ++i) - free(oids[i]); - - git_oid_shorten_free(os); - -#undef MAX_OIDS -END_TEST - -static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511"; -static char *hello_text = "hello world\n"; - -static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"; -static char *bye_text = "bye world\n"; - -BEGIN_TEST(hash0, "normal hash by blocks") - git_hash_ctx *ctx; - git_oid id1, id2; - - must_be_true((ctx = git_hash_new_ctx()) != NULL); - - /* should already be init'd */ - git_hash_update(ctx, hello_text, strlen(hello_text)); - git_hash_final(&id2, ctx); - must_pass(git_oid_mkstr(&id1, hello_id)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - - /* reinit should permit reuse */ - git_hash_init(ctx); - git_hash_update(ctx, bye_text, strlen(bye_text)); - git_hash_final(&id2, ctx); - must_pass(git_oid_mkstr(&id1, bye_id)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - - git_hash_free_ctx(ctx); -END_TEST - -BEGIN_TEST(hash1, "hash whole buffer in a single call") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, hello_id)); - - git_hash_buf(&id2, hello_text, strlen(hello_text)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(hash2, "hash a vector") - git_oid id1, id2; - git_buf_vec vec[2]; - - must_pass(git_oid_mkstr(&id1, hello_id)); - - vec[0].data = hello_text; - vec[0].len = 4; - vec[1].data = hello_text+4; - vec[1].len = strlen(hello_text)-4; - - git_hash_vec(&id2, vec, 2); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objtype0, "convert type to string") - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_BAD), "")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ__EXT1), "")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_COMMIT), "commit")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_TREE), "tree")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_BLOB), "blob")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_TAG), "tag")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ__EXT2), "")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_OFS_DELTA), "OFS_DELTA")); - must_be_true(!strcmp(git_object_type2string(GIT_OBJ_REF_DELTA), "REF_DELTA")); - - must_be_true(!strcmp(git_object_type2string(-2), "")); - must_be_true(!strcmp(git_object_type2string(8), "")); - must_be_true(!strcmp(git_object_type2string(1234), "")); -END_TEST - -BEGIN_TEST(objtype1, "convert string to type") - must_be_true(git_object_string2type(NULL) == GIT_OBJ_BAD); - must_be_true(git_object_string2type("") == GIT_OBJ_BAD); - must_be_true(git_object_string2type("commit") == GIT_OBJ_COMMIT); - must_be_true(git_object_string2type("tree") == GIT_OBJ_TREE); - must_be_true(git_object_string2type("blob") == GIT_OBJ_BLOB); - must_be_true(git_object_string2type("tag") == GIT_OBJ_TAG); - must_be_true(git_object_string2type("OFS_DELTA") == GIT_OBJ_OFS_DELTA); - must_be_true(git_object_string2type("REF_DELTA") == GIT_OBJ_REF_DELTA); - - must_be_true(git_object_string2type("CoMmIt") == GIT_OBJ_BAD); - must_be_true(git_object_string2type("hohoho") == GIT_OBJ_BAD); -END_TEST - -BEGIN_TEST(objtype2, "check if an object type is loose") - must_be_true(git_object_typeisloose(GIT_OBJ_BAD) == 0); - must_be_true(git_object_typeisloose(GIT_OBJ__EXT1) == 0); - must_be_true(git_object_typeisloose(GIT_OBJ_COMMIT) == 1); - must_be_true(git_object_typeisloose(GIT_OBJ_TREE) == 1); - must_be_true(git_object_typeisloose(GIT_OBJ_BLOB) == 1); - must_be_true(git_object_typeisloose(GIT_OBJ_TAG) == 1); - must_be_true(git_object_typeisloose(GIT_OBJ__EXT2) == 0); - must_be_true(git_object_typeisloose(GIT_OBJ_OFS_DELTA) == 0); - must_be_true(git_object_typeisloose(GIT_OBJ_REF_DELTA) == 0); - - must_be_true(git_object_typeisloose(-2) == 0); - must_be_true(git_object_typeisloose(8) == 0); - must_be_true(git_object_typeisloose(1234) == 0); -END_TEST - -BEGIN_TEST(objhash0, "hash junk data") - git_oid id, id_zero; - - must_pass(git_oid_mkstr(&id_zero, zero_id)); - - /* invalid types: */ - junk_obj.data = some_data; - must_fail(hash_object(&id, &junk_obj)); - - junk_obj.type = GIT_OBJ__EXT1; - must_fail(hash_object(&id, &junk_obj)); - - junk_obj.type = GIT_OBJ__EXT2; - must_fail(hash_object(&id, &junk_obj)); - - junk_obj.type = GIT_OBJ_OFS_DELTA; - must_fail(hash_object(&id, &junk_obj)); - - junk_obj.type = GIT_OBJ_REF_DELTA; - must_fail(hash_object(&id, &junk_obj)); - - /* data can be NULL only if len is zero: */ - junk_obj.type = GIT_OBJ_BLOB; - junk_obj.data = NULL; - must_pass(hash_object(&id, &junk_obj)); - must_be_true(git_oid_cmp(&id, &id_zero) == 0); - - junk_obj.len = 1; - must_fail(hash_object(&id, &junk_obj)); -END_TEST - -BEGIN_TEST(objhash1, "hash a commit object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, commit_id)); - - must_pass(hash_object(&id2, &commit_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash2, "hash a tree object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, tree_id)); - - must_pass(hash_object(&id2, &tree_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash3, "hash a tag object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, tag_id)); - - must_pass(hash_object(&id2, &tag_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash4, "hash a zero-length object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, zero_id)); - - must_pass(hash_object(&id2, &zero_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash5, "hash an one-byte long object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, one_id)); - - must_pass(hash_object(&id2, &one_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash6, "hash a two-byte long object") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, two_id)); - - must_pass(hash_object(&id2, &two_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_TEST(objhash7, "hash an object several bytes long") - git_oid id1, id2; - - must_pass(git_oid_mkstr(&id1, some_id)); - - must_pass(hash_object(&id2, &some_obj)); - - must_be_true(git_oid_cmp(&id1, &id2) == 0); -END_TEST - -BEGIN_SUITE(rawobjects) - ADD_TEST(oid0); - ADD_TEST(oid1); - ADD_TEST(oid2); - ADD_TEST(oid3); - ADD_TEST(oid4); - ADD_TEST(oid5); - ADD_TEST(oid6); - ADD_TEST(oid7); - ADD_TEST(oid8); - ADD_TEST(oid9); - ADD_TEST(oid10); - ADD_TEST(oid11); - ADD_TEST(oid12); - ADD_TEST(oid13); - ADD_TEST(oid14); - ADD_TEST(oid15); - ADD_TEST(oid16); - ADD_TEST(oid17); - - ADD_TEST(hash0); - ADD_TEST(hash1); - ADD_TEST(hash2); - - ADD_TEST(objtype0); - ADD_TEST(objtype1); - ADD_TEST(objtype2); - - ADD_TEST(objhash0); - ADD_TEST(objhash1); - ADD_TEST(objhash2); - ADD_TEST(objhash3); - ADD_TEST(objhash4); - ADD_TEST(objhash5); - ADD_TEST(objhash6); - ADD_TEST(objhash7); -END_SUITE - diff --git a/tests/t02-objread.c b/tests/t02-objread.c deleted file mode 100644 index 85b03b026..000000000 --- a/tests/t02-objread.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" -#include "odb.h" - -#include "t02-data.h" -#include "t02-oids.h" - - -BEGIN_TEST(existsloose0, "check if a loose object exists on the odb") - git_odb *db; - git_oid id, id2; - - must_pass(write_object_files(odb_dir, &one)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, one.id)); - - must_be_true(git_odb_exists(db, &id)); - - /* Test for a non-existant object */ - must_pass(git_oid_mkstr(&id2, "8b137891791fe96927ad78e64b0aad7bded08baa")); - must_be_true(0 == git_odb_exists(db, &id2)); - - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &one)); -END_TEST - -BEGIN_TEST(readloose0, "read a loose commit") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &commit)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, commit.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects((git_rawobj *)&obj->raw, &commit)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &commit)); -END_TEST - -BEGIN_TEST(readloose1, "read a loose tree") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &tree)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, tree.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects((git_rawobj *)&obj->raw, &tree)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &tree)); -END_TEST - -BEGIN_TEST(readloose2, "read a loose tag") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &tag)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, tag.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects((git_rawobj *)&obj->raw, &tag)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &tag)); -END_TEST - -BEGIN_TEST(readloose3, "read a loose zero-bytes object") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &zero)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, zero.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects((git_rawobj *)&obj->raw, &zero)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &zero)); -END_TEST - -BEGIN_TEST(readloose4, "read a one-byte long loose object") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &one)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, one.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects(&obj->raw, &one)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &one)); -END_TEST - -BEGIN_TEST(readloose5, "read a two-bytes long loose object") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &two)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, two.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects(&obj->raw, &two)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &two)); -END_TEST - -BEGIN_TEST(readloose6, "read a loose object which is several bytes long") - git_odb *db; - git_oid id; - git_odb_object *obj; - - must_pass(write_object_files(odb_dir, &some)); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id, some.id)); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(cmp_objects(&obj->raw, &some)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(odb_dir, &some)); -END_TEST - -BEGIN_TEST(readpack0, "read several packed objects") - unsigned int i; - git_odb *db; - - must_pass(git_odb_open(&db, ODB_FOLDER)); - - for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { - git_oid id; - git_odb_object *obj; - - must_pass(git_oid_mkstr(&id, packed_objects[i])); - must_be_true(git_odb_exists(db, &id) == 1); - must_pass(git_odb_read(&obj, db, &id)); - - git_odb_object_close(obj); - } - - git_odb_close(db); -END_TEST - -BEGIN_TEST(readheader0, "read only the header of several packed objects") - unsigned int i; - git_odb *db; - - must_pass(git_odb_open(&db, ODB_FOLDER)); - - for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { - git_oid id; - git_odb_object *obj; - size_t len; - git_otype type; - - must_pass(git_oid_mkstr(&id, packed_objects[i])); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(git_odb_read_header(&len, &type, db, &id)); - - must_be_true(obj->raw.len == len); - must_be_true(obj->raw.type == type); - - git_odb_object_close(obj); - } - - git_odb_close(db); -END_TEST - -BEGIN_TEST(readheader1, "read only the header of several loose objects") - unsigned int i; - git_odb *db; - - must_pass(git_odb_open(&db, ODB_FOLDER)); - - for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) { - git_oid id; - git_odb_object *obj; - size_t len; - git_otype type; - - must_pass(git_oid_mkstr(&id, loose_objects[i])); - - must_be_true(git_odb_exists(db, &id) == 1); - - must_pass(git_odb_read(&obj, db, &id)); - must_pass(git_odb_read_header(&len, &type, db, &id)); - - must_be_true(obj->raw.len == len); - must_be_true(obj->raw.type == type); - - git_odb_object_close(obj); - } - - git_odb_close(db); -END_TEST - -BEGIN_SUITE(objread) - ADD_TEST(existsloose0); - - ADD_TEST(readloose0); - ADD_TEST(readloose1); - ADD_TEST(readloose2); - ADD_TEST(readloose3); - ADD_TEST(readloose4); - ADD_TEST(readloose5); - ADD_TEST(readloose6); - -/* - ADD_TEST(readloose_enc0); - ADD_TEST(readloose_enc1); - ADD_TEST(readloose_enc2); - ADD_TEST(readloose_enc3); - ADD_TEST(readloose_enc4); - ADD_TEST(readloose_enc5); - ADD_TEST(readloose_enc6); -*/ - - ADD_TEST(readpack0); - - ADD_TEST(readheader0); - ADD_TEST(readheader1); -END_SUITE diff --git a/tests/t03-objwrite.c b/tests/t03-objwrite.c deleted file mode 100644 index 773887397..000000000 --- a/tests/t03-objwrite.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "fileops.h" -#include "odb.h" - -static char *odb_dir = "test-objects"; -#include "t03-data.h" - -static int make_odb_dir(void) -{ - if (gitfo_mkdir(odb_dir, 0755) < 0) { - int err = errno; - fprintf(stderr, "can't make directory \"%s\"", odb_dir); - if (err == EEXIST) - fprintf(stderr, " (already exists)"); - fprintf(stderr, "\n"); - return -1; - } - return 0; -} - -static int check_object_files(object_data *d) -{ - if (gitfo_exists(d->dir) < 0) - return -1; - if (gitfo_exists(d->file) < 0) - return -1; - return 0; -} - -static int cmp_objects(git_rawobj *o1, git_rawobj *o2) -{ - if (o1->type != o2->type) - return -1; - if (o1->len != o2->len) - return -1; - if ((o1->len > 0) && (memcmp(o1->data, o2->data, o1->len) != 0)) - return -1; - return 0; -} - -static int remove_object_files(object_data *d) -{ - if (gitfo_unlink(d->file) < 0) { - fprintf(stderr, "can't delete object file \"%s\"\n", d->file); - return -1; - } - if ((gitfo_rmdir(d->dir) < 0) && (errno != ENOTEMPTY)) { - fprintf(stderr, "can't remove directory \"%s\"\n", d->dir); - return -1; - } - - if (gitfo_rmdir(odb_dir) < 0) { - fprintf(stderr, "can't remove directory \"%s\"\n", odb_dir); - return -1; - } - - return 0; -} - -static int streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) -{ - git_odb_stream *stream; - int error; - - if ((error = git_odb_open_wstream(&stream, odb, raw->len, raw->type)) < GIT_SUCCESS) - return error; - - stream->write(stream, raw->data, raw->len); - - error = stream->finalize_write(oid, stream); - stream->free(stream); - - return error; -} - -BEGIN_TEST(write0, "write loose commit object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, commit.id)); - - must_pass(streaming_write(&id2, db, &commit_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&commit)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &commit_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&commit)); -END_TEST - -BEGIN_TEST(write1, "write loose tree object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, tree.id)); - - must_pass(streaming_write(&id2, db, &tree_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&tree)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &tree_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&tree)); -END_TEST - -BEGIN_TEST(write2, "write loose tag object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, tag.id)); - - must_pass(streaming_write(&id2, db, &tag_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&tag)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &tag_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&tag)); -END_TEST - -BEGIN_TEST(write3, "write zero-length object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, zero.id)); - - must_pass(streaming_write(&id2, db, &zero_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&zero)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &zero_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&zero)); -END_TEST - -BEGIN_TEST(write4, "write one-byte long object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, one.id)); - - must_pass(streaming_write(&id2, db, &one_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&one)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &one_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&one)); -END_TEST - -BEGIN_TEST(write5, "write two-byte long object") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, two.id)); - - must_pass(streaming_write(&id2, db, &two_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&two)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &two_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&two)); -END_TEST - -BEGIN_TEST(write6, "write an object which is several bytes long") - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - - must_pass(make_odb_dir()); - must_pass(git_odb_open(&db, odb_dir)); - must_pass(git_oid_mkstr(&id1, some.id)); - - must_pass(streaming_write(&id2, db, &some_obj)); - must_be_true(git_oid_cmp(&id1, &id2) == 0); - must_pass(check_object_files(&some)); - - must_pass(git_odb_read(&obj, db, &id1)); - must_pass(cmp_objects(&obj->raw, &some_obj)); - - git_odb_object_close(obj); - git_odb_close(db); - must_pass(remove_object_files(&some)); -END_TEST - -BEGIN_SUITE(objwrite) - ADD_TEST(write0); - ADD_TEST(write1); - ADD_TEST(write2); - ADD_TEST(write3); - ADD_TEST(write4); - ADD_TEST(write5); - ADD_TEST(write6); -END_SUITE diff --git a/tests/t04-commit.c b/tests/t04-commit.c deleted file mode 100644 index 36f3e66b5..000000000 --- a/tests/t04-commit.c +++ /dev/null @@ -1,554 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "commit.h" -#include "signature.h" - -static char *test_commits_broken[] = { - -/* empty commit */ -"", - -/* random garbage */ -"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n", - -/* broken endlines 1 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\ -author Vicent Marti 1273848544 +0200\r\n\ -committer Vicent Marti 1273848544 +0200\r\n\ -\r\n\ -a test commit with broken endlines\r\n", - -/* broken endlines 2 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\ -author Vicent Marti 1273848544 +0200\ -committer Vicent Marti 1273848544 +0200\ -\ -another test commit with broken endlines", - -/* starting endlines */ -"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a test commit with a starting endline\n", - -/* corrupted commit 1 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent 05452d6349abcd67aa396df", - -/* corrupted commit 2 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent ", - -/* corrupted commit 3 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent ", - -/* corrupted commit 4 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -par", - -}; - - -static char *test_commits_working[] = { -/* simple commit with no message */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n", - -/* simple commit, no parent */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works\n", - -/* simple commit, no parent, no newline in message */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works", - -/* simple commit, 1 parent */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -parent e90810b8df3e80c413d903f631643c716887138d\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works\n", -}; - -BEGIN_TEST(parse0, "parse the OID line in a commit") - - git_oid oid; - -#define TEST_OID_PASS(string, header) { \ - const char *ptr = string;\ - const char *ptr_original = ptr;\ - size_t len = strlen(ptr);\ - must_pass(git__parse_oid(&oid, &ptr, ptr + len, header));\ - must_be_true(ptr == ptr_original + len);\ -} - -#define TEST_OID_FAIL(string, header) { \ - const char *ptr = string;\ - size_t len = strlen(ptr);\ - must_fail(git__parse_oid(&oid, &ptr, ptr + len, header));\ -} - - TEST_OID_PASS("parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent "); - TEST_OID_PASS("tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree "); - TEST_OID_PASS("random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading "); - TEST_OID_PASS("stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading"); - TEST_OID_PASS("tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree "); - TEST_OID_PASS("tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree "); - TEST_OID_PASS("tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree "); - TEST_OID_PASS("tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree "); - - TEST_OID_FAIL("parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent "); - TEST_OID_FAIL("05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree "); - TEST_OID_FAIL("parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent "); - TEST_OID_FAIL("parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent "); - TEST_OID_FAIL("tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree "); - TEST_OID_FAIL("parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent "); - TEST_OID_FAIL("parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent "); - TEST_OID_FAIL("", "tree "); - TEST_OID_FAIL("", ""); - -#undef TEST_OID_PASS -#undef TEST_OID_FAIL - -END_TEST - -BEGIN_TEST(parse1, "parse the signature line in a commit") - -#define TEST_SIGNATURE_PASS(_string, _header, _name, _email, _time, _offset) { \ - const char *ptr = _string; \ - size_t len = strlen(_string);\ - git_signature person = {NULL, NULL, {0, 0}}; \ - must_pass(git_signature__parse(&person, &ptr, ptr + len, _header));\ - must_be_true(strcmp(_name, person.name) == 0);\ - must_be_true(strcmp(_email, person.email) == 0);\ - must_be_true(_time == person.when.time);\ - must_be_true(_offset == person.when.offset);\ - free(person.name); free(person.email);\ -} - -#define TEST_SIGNATURE_FAIL(_string, _header) { \ - const char *ptr = _string; \ - size_t len = strlen(_string);\ - git_signature person = {NULL, NULL, {0, 0}}; \ - must_fail(git_signature__parse(&person, &ptr, ptr + len, _header));\ - free(person.name); free(person.email);\ -} - - TEST_SIGNATURE_PASS( - "author Vicent Marti 12345 \n", - "author ", - "Vicent Marti", - "tanoku@gmail.com", - 12345, - 0); - - TEST_SIGNATURE_PASS( - "author Vicent Marti <> 12345 \n", - "author ", - "Vicent Marti", - "", - 12345, - 0); - - TEST_SIGNATURE_PASS( - "author Vicent Marti 231301 +1020\n", - "author ", - "Vicent Marti", - "tanoku@gmail.com", - 231301, - 620); - - TEST_SIGNATURE_PASS( - "author Vicent Marti with an outrageously long name \ - which will probably overflow the buffer 12345 \n", - "author ", - "Vicent Marti with an outrageously long name \ - which will probably overflow the buffer", - "tanoku@gmail.com", - 12345, - 0); - - TEST_SIGNATURE_PASS( - "author Vicent Marti 12345 \n", - "author ", - "Vicent Marti", - "tanokuwithaveryveryverylongemail\ - whichwillprobablyvoverflowtheemailbuffer@gmail.com", - 12345, - 0); - - TEST_SIGNATURE_PASS( - "committer Vicent Marti 123456 +0000 \n", - "committer ", - "Vicent Marti", - "tanoku@gmail.com", - 123456, - 0); - - TEST_SIGNATURE_PASS( - "committer Vicent Marti 123456 +0100 \n", - "committer ", - "Vicent Marti", - "tanoku@gmail.com", - 123456, - 60); - - TEST_SIGNATURE_PASS( - "committer Vicent Marti 123456 -0100 \n", - "committer ", - "Vicent Marti", - "tanoku@gmail.com", - 123456, - -60); - - TEST_SIGNATURE_FAIL( - "committer Vicent Marti 123456 -1500 \n", - "committer "); - - TEST_SIGNATURE_FAIL( - "committer Vicent Marti 123456 +0163 \n", - "committer "); - - TEST_SIGNATURE_FAIL( - "author Vicent Marti 12345 \n", - "author "); - - TEST_SIGNATURE_FAIL( - "author Vicent Marti 12345 \n", - "committer "); - - TEST_SIGNATURE_FAIL( - "author Vicent Marti 12345 \n", - "author "); - - TEST_SIGNATURE_FAIL( - "author Vicent Marti notime \n", - "author "); - - TEST_SIGNATURE_FAIL( - "author Vicent Marti \n", - "author "); - - TEST_SIGNATURE_FAIL( - "author ", - "author "); - -#undef TEST_SIGNATURE_PASS -#undef TEST_SIGNATURE_FAIL - -END_TEST - -/* External declaration for testing the buffer parsing method */ -int commit_parse_buffer(git_commit *commit, void *data, size_t len, unsigned int parse_flags); - -BEGIN_TEST(parse2, "parse a whole commit buffer") - const int broken_commit_count = sizeof(test_commits_broken) / sizeof(*test_commits_broken); - const int working_commit_count = sizeof(test_commits_working) / sizeof(*test_commits_working); - - int i; - git_repository *repo; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - for (i = 0; i < broken_commit_count; ++i) { - git_commit *commit; - commit = git__malloc(sizeof(git_commit)); - memset(commit, 0x0, sizeof(git_commit)); - commit->object.repo = repo; - - must_fail(commit_parse_buffer( - commit, - test_commits_broken[i], - strlen(test_commits_broken[i]), - 0x1) - ); - - git_commit__free(commit); - } - - for (i = 0; i < working_commit_count; ++i) { - git_commit *commit; - - commit = git__malloc(sizeof(git_commit)); - memset(commit, 0x0, sizeof(git_commit)); - commit->object.repo = repo; - - must_pass(commit_parse_buffer( - commit, - test_commits_working[i], - strlen(test_commits_working[i]), - 0x0) - ); - - git_commit__free(commit); - - commit = git__malloc(sizeof(git_commit)); - memset(commit, 0x0, sizeof(git_commit)); - commit->object.repo = repo; - - must_pass(commit_parse_buffer( - commit, - test_commits_working[i], - strlen(test_commits_working[i]), - 0x1) - ); - - git_commit__free(commit); - } - - git_repository_free(repo); -END_TEST - -static const char *commit_ids[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ -}; - -BEGIN_TEST(details0, "query the details on a parsed commit") - const size_t commit_count = sizeof(commit_ids) / sizeof(const char *); - - unsigned int i; - git_repository *repo; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - for (i = 0; i < commit_count; ++i) { - git_oid id; - git_commit *commit; - - const git_signature *author, *committer; - const char *message, *message_short; - git_time_t commit_time; - unsigned int parents, p; - git_commit *parent = NULL, *old_parent = NULL; - - git_oid_mkstr(&id, commit_ids[i]); - - must_pass(git_commit_lookup(&commit, repo, &id)); - - message = git_commit_message(commit); - message_short = git_commit_message_short(commit); - author = git_commit_author(commit); - committer = git_commit_committer(commit); - commit_time = git_commit_time(commit); - parents = git_commit_parentcount(commit); - - must_be_true(strcmp(author->name, "Scott Chacon") == 0); - must_be_true(strcmp(author->email, "schacon@gmail.com") == 0); - must_be_true(strcmp(committer->name, "Scott Chacon") == 0); - must_be_true(strcmp(committer->email, "schacon@gmail.com") == 0); - must_be_true(strchr(message, '\n') != NULL); - must_be_true(strchr(message_short, '\n') == NULL); - must_be_true(commit_time > 0); - must_be_true(parents <= 2); - for (p = 0;p < parents;p++) { - if (old_parent != NULL) - git_commit_close(old_parent); - - old_parent = parent; - must_pass(git_commit_parent(&parent, commit, p)); - must_be_true(parent != NULL); - must_be_true(git_commit_author(parent) != NULL); // is it really a commit? - } - git_commit_close(old_parent); - git_commit_close(parent); - - must_fail(git_commit_parent(&parent, commit, parents)); - git_commit_close(commit); - } - - git_repository_free(repo); -END_TEST - -#define COMMITTER_NAME "Vicent Marti" -#define COMMITTER_EMAIL "vicent@github.com" -#define COMMIT_MESSAGE "This commit has been created in memory\n\ -This is a commit created in memory and it will be written back to disk\n" - -static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; - - -BEGIN_TEST(write0, "write a new commit object from memory to disk") - git_repository *repo; - git_commit *commit; - git_oid tree_id, parent_id, commit_id; - const git_signature *author, *committer; - /* char hex_oid[41]; */ - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - - git_oid_mkstr(&tree_id, tree_oid); - git_oid_mkstr(&parent_id, commit_ids[4]); - - /* create signatures */ - committer = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60); - must_be_true(committer != NULL); - - author = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90); - must_be_true(author != NULL); - - must_pass(git_commit_create_v( - &commit_id, /* out id */ - repo, - NULL, /* do not update the HEAD */ - author, - committer, - COMMIT_MESSAGE, - &tree_id, - 1, &parent_id)); - - git_signature_free((git_signature *)committer); - git_signature_free((git_signature *)author); - - must_pass(git_commit_lookup(&commit, repo, &commit_id)); - - /* Check attributes were set correctly */ - author = git_commit_author(commit); - must_be_true(author != NULL); - must_be_true(strcmp(author->name, COMMITTER_NAME) == 0); - must_be_true(strcmp(author->email, COMMITTER_EMAIL) == 0); - must_be_true(author->when.time == 987654321); - must_be_true(author->when.offset == 90); - - committer = git_commit_committer(commit); - must_be_true(committer != NULL); - must_be_true(strcmp(committer->name, COMMITTER_NAME) == 0); - must_be_true(strcmp(committer->email, COMMITTER_EMAIL) == 0); - must_be_true(committer->when.time == 123456789); - must_be_true(committer->when.offset == 60); - - must_be_true(strcmp(git_commit_message(commit), COMMIT_MESSAGE) == 0); - - must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); - - git_commit_close(commit); - git_repository_free(repo); -END_TEST - -#define ROOT_COMMIT_MESSAGE "This is a root commit\n\ -This is a root commit and should be the only one in this branch\n" - -BEGIN_TEST(root0, "create a root commit") - git_repository *repo; - git_commit *commit; - git_oid tree_id, commit_id; - const git_oid *branch_oid; - const git_signature *author, *committer; - const char *branch_name = "refs/heads/root-commit-branch"; - git_reference *head, *branch; - char *head_old; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&tree_id, tree_oid); - - /* create signatures */ - committer = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60); - must_be_true(committer != NULL); - - author = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90); - must_be_true(author != NULL); - - /* First we need to update HEAD so it points to our non-existant branch */ - must_pass(git_reference_lookup(&head, repo, "HEAD")); - must_be_true(git_reference_type(head) == GIT_REF_SYMBOLIC); - head_old = git__strdup(git_reference_target(head)); - must_be_true(head_old != NULL); - - must_pass(git_reference_set_target(head, branch_name)); - - must_pass(git_commit_create_v( - &commit_id, /* out id */ - repo, - "HEAD", - author, - committer, - ROOT_COMMIT_MESSAGE, - &tree_id, - 0)); - - git_signature_free((git_signature *)committer); - git_signature_free((git_signature *)author); - - /* - * The fact that creating a commit works has already been - * tested. Here we just make sure it's our commit and that it was - * written as a root commit. - */ - must_pass(git_commit_lookup(&commit, repo, &commit_id)); - must_be_true(git_commit_parentcount(commit) == 0); - must_pass(git_reference_lookup(&branch, repo, branch_name)); - branch_oid = git_reference_oid(branch); - must_pass(git_oid_cmp(branch_oid, &commit_id)); - must_be_true(!strcmp(git_commit_message(commit), ROOT_COMMIT_MESSAGE)); - - /* Remove the data we just added to the repo */ - must_pass(git_reference_lookup(&head, repo, "HEAD")); - must_pass(git_reference_set_target(head, head_old)); - must_pass(git_reference_delete(branch)); - must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); - free(head_old); - git_commit_close(commit); - git_repository_free(repo); -END_TEST - -BEGIN_SUITE(commit) - ADD_TEST(parse0); - ADD_TEST(parse1); - ADD_TEST(parse2); - ADD_TEST(details0); - - ADD_TEST(write0); - //ADD_TEST(write1); - - ADD_TEST(root0); -END_SUITE diff --git a/tests/t05-revwalk.c b/tests/t05-revwalk.c deleted file mode 100644 index cfcf01066..000000000 --- a/tests/t05-revwalk.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -/* - $ git log --oneline --graph --decorate - * a4a7dce (HEAD, br2) Merge branch 'master' into br2 - |\ - | * 9fd738e (master) a fourth commit - | * 4a202b3 a third commit - * | c47800c branch commit one - |/ - * 5b5b025 another commit - * 8496071 testing -*/ -static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; - -static const char *commit_ids[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ -}; - -/* Careful: there are two possible topological sorts */ -static const int commit_sorting_topo[][6] = { - {0, 1, 2, 3, 5, 4}, {0, 3, 1, 2, 5, 4} -}; - -static const int commit_sorting_time[][6] = { - {0, 3, 1, 2, 5, 4} -}; - -static const int commit_sorting_topo_reverse[][6] = { - {4, 5, 3, 2, 1, 0}, {4, 5, 2, 1, 3, 0} -}; - -static const int commit_sorting_time_reverse[][6] = { - {4, 5, 2, 1, 3, 0} -}; - -#define commit_count 6 -static const int result_bytes = 24; - - -static int get_commit_index(git_oid *raw_oid) -{ - int i; - char oid[40]; - - git_oid_fmt(oid, raw_oid); - - for (i = 0; i < commit_count; ++i) - if (memcmp(oid, commit_ids[i], 40) == 0) - return i; - - return -1; -} - -static int test_walk(git_revwalk *walk, const git_oid *root, - int flags, const int possible_results[][6], int results_count) -{ - git_oid oid; - - int i; - int result_array[commit_count]; - - git_revwalk_sorting(walk, flags); - git_revwalk_push(walk, root); - - for (i = 0; i < commit_count; ++i) - result_array[i] = -1; - - i = 0; - - while (git_revwalk_next(&oid, walk) == GIT_SUCCESS) { - result_array[i++] = get_commit_index(&oid); - /*{ - char str[41]; - git_oid_fmt(str, &oid); - str[40] = 0; - printf(" %d) %s\n", i, str); - }*/ - } - - for (i = 0; i < results_count; ++i) - if (memcmp(possible_results[i], - result_array, result_bytes) == 0) - return GIT_SUCCESS; - - return GIT_ERROR; -} - -BEGIN_TEST(walk0, "do a simple walk on a repo with different sorting modes") - git_oid id; - git_repository *repo; - git_revwalk *walk; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_pass(git_revwalk_new(&walk, repo)); - - git_oid_mkstr(&id, commit_head); - - must_pass(test_walk(walk, &id, GIT_SORT_TIME, commit_sorting_time, 1)); - must_pass(test_walk(walk, &id, GIT_SORT_TOPOLOGICAL, commit_sorting_topo, 2)); - must_pass(test_walk(walk, &id, GIT_SORT_TIME | GIT_SORT_REVERSE, commit_sorting_time_reverse, 1)); - must_pass(test_walk(walk, &id, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE, commit_sorting_topo_reverse, 2)); - - git_revwalk_free(walk); - git_repository_free(repo); -END_TEST - -BEGIN_SUITE(revwalk) - ADD_TEST(walk0); -END_SUITE diff --git a/tests/t06-index.c b/tests/t06-index.c deleted file mode 100644 index 25adbf7c3..000000000 --- a/tests/t06-index.c +++ /dev/null @@ -1,188 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "index.h" - -#define TEST_INDEX_ENTRY_COUNT 109 -#define TEST_INDEX2_ENTRY_COUNT 1437 - -struct test_entry { - unsigned int index; - char path[128]; - git_off_t file_size; - git_time_t mtime; -}; - -struct test_entry TEST_ENTRIES[] = { - {4, "Makefile", 5064, 0x4C3F7F33}, - {62, "tests/Makefile", 2631, 0x4C3F7F33}, - {36, "src/index.c", 10014, 0x4C43368D}, - {6, "git.git-authors", 2709, 0x4C3F7F33}, - {48, "src/revobject.h", 1448, 0x4C3F7FE2} -}; - -BEGIN_TEST(read0, "load an empty index") - git_index *index; - - must_pass(git_index_open(&index, "in-memory-index")); - must_be_true(index->on_disk == 0); - - must_pass(git_index_read(index)); - - must_be_true(index->on_disk == 0); - must_be_true(git_index_entrycount(index) == 0); - must_be_true(index->entries.sorted); - - git_index_free(index); -END_TEST - -BEGIN_TEST(read1, "load a standard index (default test index)") - git_index *index; - unsigned int i; - git_index_entry **entries; - - must_pass(git_index_open(&index, TEST_INDEX_PATH)); - must_be_true(index->on_disk); - - must_pass(git_index_read(index)); - - must_be_true(index->on_disk); - must_be_true(git_index_entrycount(index) == TEST_INDEX_ENTRY_COUNT); - must_be_true(index->entries.sorted); - - 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]; - - must_be_true(strcmp(e->path, TEST_ENTRIES[i].path) == 0); - must_be_true(e->mtime.seconds == TEST_ENTRIES[i].mtime); - must_be_true(e->file_size == TEST_ENTRIES[i].file_size); - } - - git_index_free(index); -END_TEST - -BEGIN_TEST(read2, "load a standard index (git.git index)") - git_index *index; - - must_pass(git_index_open(&index, TEST_INDEX2_PATH)); - must_be_true(index->on_disk); - - must_pass(git_index_read(index)); - - must_be_true(index->on_disk); - must_be_true(git_index_entrycount(index) == TEST_INDEX2_ENTRY_COUNT); - must_be_true(index->entries.sorted); - must_be_true(index->tree != NULL); - - git_index_free(index); -END_TEST - -BEGIN_TEST(find0, "find an entry on an index") - git_index *index; - unsigned int i; - - must_pass(git_index_open(&index, TEST_INDEX_PATH)); - must_pass(git_index_read(index)); - - for (i = 0; i < ARRAY_SIZE(TEST_ENTRIES); ++i) { - int idx = git_index_find(index, TEST_ENTRIES[i].path); - must_be_true((unsigned int)idx == TEST_ENTRIES[i].index); - } - - git_index_free(index); -END_TEST - -BEGIN_TEST(find1, "find an entry in an empty index") - git_index *index; - unsigned int i; - - must_pass(git_index_open(&index, "fake-index")); - - for (i = 0; i < ARRAY_SIZE(TEST_ENTRIES); ++i) { - int idx = git_index_find(index, TEST_ENTRIES[i].path); - must_be_true(idx == GIT_ENOTFOUND); - } - - git_index_free(index); -END_TEST - -BEGIN_TEST(write0, "write an index back to disk") - git_index *index; - - must_pass(copy_file(TEST_INDEXBIG_PATH, "index_rewrite")); - - must_pass(git_index_open(&index, "index_rewrite")); - must_pass(git_index_read(index)); - must_be_true(index->on_disk); - - must_pass(git_index_write(index)); - must_pass(cmp_files(TEST_INDEXBIG_PATH, "index_rewrite")); - - git_index_free(index); - - gitfo_unlink("index_rewrite"); -END_TEST - -BEGIN_TEST(sort0, "sort the entires in an index") - /* - * TODO: This no longer applies: - * index sorting in Git uses some specific changes to the way - * directories are sorted. - * - * We need to specificially check for this by creating a new - * index, adding entries in random order and then - * checking for consistency - */ -END_TEST - - -BEGIN_TEST(sort1, "sort the entires in an empty index") - git_index *index; - - must_pass(git_index_open(&index, "fake-index")); - - /* FIXME: this test is slightly dumb */ - must_be_true(index->entries.sorted); - - git_index_free(index); -END_TEST - -BEGIN_SUITE(index) - ADD_TEST(read0); - ADD_TEST(read1); - ADD_TEST(read2); - - ADD_TEST(find0); - ADD_TEST(find1); - - ADD_TEST(write0); - - ADD_TEST(sort0); - ADD_TEST(sort1); -END_SUITE diff --git a/tests/t07-hashtable.c b/tests/t07-hashtable.c deleted file mode 100644 index 0b362cafd..000000000 --- a/tests/t07-hashtable.c +++ /dev/null @@ -1,193 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "hashtable.h" -#include "hash.h" - -typedef struct _aux_object { - int __bulk; - git_oid id; - int visited; -} table_item; - -uint32_t hash_func(const void *key, int hash_id) -{ - uint32_t r; - git_oid *id; - - id = (git_oid *)key; - memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r)); - return r; -} - -int hash_cmpkey(const void *a, const void *b) -{ - return git_oid_cmp(a, b); -} - -BEGIN_TEST(table0, "create a new hashtable") - - git_hashtable *table = NULL; - - table = git_hashtable_alloc(55, hash_func, hash_cmpkey); - must_be_true(table != NULL); - must_be_true(table->size_mask + 1 == 64); - - git_hashtable_free(table); - -END_TEST - -BEGIN_TEST(table1, "fill the hashtable with random entries") - - const int objects_n = 32; - int i; - - table_item *objects; - git_hashtable *table = NULL; - - table = git_hashtable_alloc(objects_n * 2, hash_func, hash_cmpkey); - must_be_true(table != NULL); - - objects = git__malloc(objects_n * sizeof(table_item)); - memset(objects, 0x0, objects_n * sizeof(table_item)); - - /* populate the hash table */ - for (i = 0; i < objects_n; ++i) { - git_hash_buf(&(objects[i].id), &i, sizeof(int)); - must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i]))); - } - - /* make sure all the inserted objects can be found */ - for (i = 0; i < objects_n; ++i) { - git_oid id; - table_item *ob; - - git_hash_buf(&id, &i, sizeof(int)); - ob = (table_item *)git_hashtable_lookup(table, &id); - - must_be_true(ob != NULL); - must_be_true(ob == &(objects[i])); - } - - /* make sure we cannot find inexisting objects */ - for (i = 0; i < 50; ++i) { - int hash_id; - git_oid id; - - hash_id = (rand() % 50000) + objects_n; - git_hash_buf(&id, &hash_id, sizeof(int)); - must_be_true(git_hashtable_lookup(table, &id) == NULL); - } - - git_hashtable_free(table); - free(objects); - -END_TEST - - -BEGIN_TEST(table2, "make sure the table resizes automatically") - - const int objects_n = 64; - int i; - unsigned int old_size; - table_item *objects; - git_hashtable *table = NULL; - - table = git_hashtable_alloc(objects_n, hash_func, hash_cmpkey); - must_be_true(table != NULL); - - objects = git__malloc(objects_n * sizeof(table_item)); - memset(objects, 0x0, objects_n * sizeof(table_item)); - - old_size = table->size_mask + 1; - - /* populate the hash table -- should be automatically resized */ - for (i = 0; i < objects_n; ++i) { - git_hash_buf(&(objects[i].id), &i, sizeof(int)); - must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i]))); - } - - must_be_true(table->size_mask > old_size); - - /* make sure all the inserted objects can be found */ - for (i = 0; i < objects_n; ++i) { - git_oid id; - table_item *ob; - - git_hash_buf(&id, &i, sizeof(int)); - ob = (table_item *)git_hashtable_lookup(table, &id); - - must_be_true(ob != NULL); - must_be_true(ob == &(objects[i])); - } - - git_hashtable_free(table); - free(objects); - -END_TEST - -BEGIN_TEST(tableit0, "iterate through all the contents of the table") - - const int objects_n = 32; - int i; - table_item *objects, *ob; - const void *GIT_UNUSED(_unused); - - git_hashtable *table = NULL; - - table = git_hashtable_alloc(objects_n * 2, hash_func, hash_cmpkey); - must_be_true(table != NULL); - - objects = git__malloc(objects_n * sizeof(table_item)); - memset(objects, 0x0, objects_n * sizeof(table_item)); - - /* populate the hash table */ - for (i = 0; i < objects_n; ++i) { - git_hash_buf(&(objects[i].id), &i, sizeof(int)); - must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i]))); - } - - GIT_HASHTABLE_FOREACH(table, _unused, ob, - ob->visited = 1; - ); - - /* make sure all nodes have been visited */ - for (i = 0; i < objects_n; ++i) - must_be_true(objects[i].visited); - - git_hashtable_free(table); - free(objects); -END_TEST - - -BEGIN_SUITE(hashtable) - ADD_TEST(table0); - ADD_TEST(table1); - ADD_TEST(table2); - ADD_TEST(tableit0); -END_SUITE - diff --git a/tests/t08-tag.c b/tests/t08-tag.c deleted file mode 100644 index fae2e99db..000000000 --- a/tests/t08-tag.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "tag.h" - -static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; -static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; -static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; - -BEGIN_TEST(read0, "read and parse a tag from the repository") - git_repository *repo; - git_tag *tag1, *tag2; - git_commit *commit; - git_oid id1, id2, id_commit; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&id1, tag1_id); - git_oid_mkstr(&id2, tag2_id); - git_oid_mkstr(&id_commit, tagged_commit); - - must_pass(git_tag_lookup(&tag1, repo, &id1)); - - must_be_true(strcmp(git_tag_name(tag1), "test") == 0); - must_be_true(git_tag_type(tag1) == GIT_OBJ_TAG); - - must_pass(git_tag_target((git_object **)&tag2, tag1)); - must_be_true(tag2 != NULL); - - must_be_true(git_oid_cmp(&id2, git_tag_id(tag2)) == 0); - - must_pass(git_tag_target((git_object **)&commit, tag2)); - must_be_true(commit != NULL); - - must_be_true(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); - - git_tag_close(tag1); - git_tag_close(tag2); - git_commit_close(commit); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(read1, "list all tag names from the repository") - git_repository *repo; - git_strarray tag_list; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_pass(git_tag_list(&tag_list, repo)); - - must_be_true(tag_list.count == 3); - - git_strarray_free(&tag_list); - git_repository_free(repo); -END_TEST - - -#define TAGGER_NAME "Vicent Marti" -#define TAGGER_EMAIL "vicent@github.com" -#define TAGGER_MESSAGE "This is my tag.\n\nThere are many tags, but this one is mine\n" - -BEGIN_TEST(write0, "write a tag to the repository and read it again") - git_repository *repo; - git_tag *tag; - git_oid target_id, tag_id; - const git_signature *tagger; - git_reference *ref_tag; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&target_id, tagged_commit); - - /* create signature */ - tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); - must_be_true(tagger != NULL); - - must_pass(git_tag_create( - &tag_id, /* out id */ - repo, - "the-tag", - &target_id, - GIT_OBJ_COMMIT, - tagger, - TAGGER_MESSAGE)); - - git_signature_free((git_signature *)tagger); - - must_pass(git_tag_lookup(&tag, repo, &tag_id)); - must_be_true(git_oid_cmp(git_tag_target_oid(tag), &target_id) == 0); - - /* Check attributes were set correctly */ - tagger = git_tag_tagger(tag); - must_be_true(tagger != NULL); - must_be_true(strcmp(tagger->name, TAGGER_NAME) == 0); - must_be_true(strcmp(tagger->email, TAGGER_EMAIL) == 0); - must_be_true(tagger->when.time == 123456789); - must_be_true(tagger->when.offset == 60); - - must_be_true(strcmp(git_tag_message(tag), TAGGER_MESSAGE) == 0); - - must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/the-tag")); - must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0); - must_pass(git_reference_delete(ref_tag)); - - must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)tag)); - - git_tag_close(tag); - git_repository_free(repo); - -END_TEST - -BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid should fail") - git_repository *repo; - git_oid target_id, tag_id; - const git_signature *tagger; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&target_id, "deadbeef1b46c854b31185ea97743be6a8774479"); - - /* create signature */ - tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); - must_be_true(tagger != NULL); - - must_fail(git_tag_create( - &tag_id, /* out id */ - repo, - "the-zombie-tag", - &target_id, - GIT_OBJ_COMMIT, - tagger, - TAGGER_MESSAGE)); - - git_signature_free((git_signature *)tagger); - - git_repository_free(repo); - -END_TEST - -BEGIN_TEST(write2, "Attempt to write a tag bearing the same name than an already existing tag") - git_repository *repo; - git_oid target_id, tag_id; - const git_signature *tagger; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&target_id, tagged_commit); - - /* create signature */ - tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); - must_be_true(tagger != NULL); - - must_fail(git_tag_create( - &tag_id, /* out id */ - repo, - "e90810b", - &target_id, - GIT_OBJ_COMMIT, - tagger, - TAGGER_MESSAGE)); - - git_signature_free((git_signature *)tagger); - - git_repository_free(repo); - -END_TEST - -BEGIN_TEST(write3, "Replace an already existing tag") - git_repository *repo; - git_oid target_id, tag_id, old_tag_id; - const git_signature *tagger; - git_reference *ref_tag; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&target_id, tagged_commit); - - must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b")); - git_oid_cpy(&old_tag_id, git_reference_oid(ref_tag)); - - /* create signature */ - tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); - must_be_true(tagger != NULL); - - must_pass(git_tag_create_f( - &tag_id, /* out id */ - repo, - "e90810b", - &target_id, - GIT_OBJ_COMMIT, - tagger, - TAGGER_MESSAGE)); - - git_signature_free((git_signature *)tagger); - - must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b")); - must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0); - must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &old_tag_id) != 0); - - close_temp_repo(repo); - -END_TEST - -BEGIN_TEST(write4, "Delete an already existing tag") - git_repository *repo; - git_reference *ref_tag; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - must_pass(git_tag_delete(repo,"e90810b")); - - must_fail(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b")); - - close_temp_repo(repo); - -END_TEST - -BEGIN_SUITE(tag) - ADD_TEST(read0); - ADD_TEST(read1); - ADD_TEST(write0); - ADD_TEST(write1); - ADD_TEST(write2); - ADD_TEST(write3); - ADD_TEST(write4); -END_SUITE diff --git a/tests/t09-tree.c b/tests/t09-tree.c deleted file mode 100644 index af992fdb3..000000000 --- a/tests/t09-tree.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "tree.h" - -static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; - -static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; -static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; -static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; -static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; - -#if 0 -static int print_tree(git_repository *repo, const git_oid *tree_oid, int depth) -{ - static const char *indent = " "; - git_tree *tree; - unsigned int i; - - if (git_tree_lookup(&tree, repo, tree_oid) < GIT_SUCCESS) - return GIT_ERROR; - - for (i = 0; i < git_tree_entrycount(tree); ++i) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - char entry_oid[40]; - - git_oid_fmt(entry_oid, &entry->oid); - printf("%.*s%o [%.*s] %s\n", depth*2, indent, entry->attr, 40, entry_oid, entry->filename); - - if (entry->attr == S_IFDIR) { - if (print_tree(repo, &entry->oid, depth + 1) < GIT_SUCCESS) { - git_tree_close(tree); - return GIT_ERROR; - } - } - } - - git_tree_close(tree); - return GIT_SUCCESS; -} -#endif - -BEGIN_TEST(read0, "acces randomly the entries on a loaded tree") - git_oid id; - git_repository *repo; - git_tree *tree; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&id, tree_oid); - - must_pass(git_tree_lookup(&tree, repo, &id)); - - must_be_true(git_tree_entry_byname(tree, "README") != NULL); - must_be_true(git_tree_entry_byname(tree, "NOTEXISTS") == NULL); - must_be_true(git_tree_entry_byname(tree, "") == NULL); - must_be_true(git_tree_entry_byindex(tree, 0) != NULL); - must_be_true(git_tree_entry_byindex(tree, 2) != NULL); - must_be_true(git_tree_entry_byindex(tree, 3) == NULL); - must_be_true(git_tree_entry_byindex(tree, -1) == NULL); - - git_tree_close(tree); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(read1, "read a tree from the repository") - git_oid id; - git_repository *repo; - git_tree *tree; - const git_tree_entry *entry; - git_object *obj; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - git_oid_mkstr(&id, tree_oid); - - must_pass(git_tree_lookup(&tree, repo, &id)); - - must_be_true(git_tree_entrycount(tree) == 3); - - /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ - must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_TREE) == 0); - git_object_close(obj); - must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_BLOB) == GIT_EINVALIDTYPE); - git_object_close(obj); - - entry = git_tree_entry_byname(tree, "README"); - must_be_true(entry != NULL); - - must_be_true(strcmp(git_tree_entry_name(entry), "README") == 0); - - must_pass(git_tree_entry_2object(&obj, repo, entry)); - - git_object_close(obj); - git_tree_close(tree); - git_repository_free(repo); -END_TEST - -#if 0 -BEGIN_TEST(write0, "write a tree from an index") - git_repository *repo; - git_index *index; - git_oid tree_oid; - - must_pass(git_repository_open(&repo, "/tmp/redtmp/.git")); - must_pass(git_repository_index(&index, repo)); - - must_pass(git_tree_create_fromindex(&tree_oid, index)); - must_pass(print_tree(repo, &tree_oid, 0)); - - git_repository_free(repo); -END_TEST -#endif - -BEGIN_TEST(write2, "write a tree from a memory") - git_repository *repo; - git_treebuilder *builder; - git_tree *tree; - git_oid id, bid, rid, id2; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - git_oid_mkstr(&id, first_tree); - git_oid_mkstr(&id2, second_tree); - git_oid_mkstr(&bid, blob_oid); - - //create a second tree from first tree using `git_treebuilder_insert` on REPOSITORY_FOLDER. - must_pass(git_tree_lookup(&tree, repo, &id)); - must_pass(git_treebuilder_create(&builder, tree)); - must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); - must_pass(git_treebuilder_write(&rid,repo,builder)); - - must_be_true(git_oid_cmp(&rid, &id2) == 0); - - git_treebuilder_free(builder); - git_tree_close(tree); - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(write3, "write a hierarchical tree from a memory") - git_repository *repo; - git_treebuilder *builder; - git_tree *tree; - git_oid id, bid, subtree_id, id2, id3; - git_oid id_hiearar; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - git_oid_mkstr(&id, first_tree); - git_oid_mkstr(&id2, second_tree); - git_oid_mkstr(&id3, third_tree); - git_oid_mkstr(&bid, blob_oid); - - //create subtree - must_pass(git_treebuilder_create(&builder, NULL)); - must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); - must_pass(git_treebuilder_write(&subtree_id,repo,builder)); - git_treebuilder_free(builder); - - // create parent tree - must_pass(git_tree_lookup(&tree, repo, &id)); - must_pass(git_treebuilder_create(&builder, tree)); - must_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); - must_pass(git_treebuilder_write(&id_hiearar,repo,builder)); - git_treebuilder_free(builder); - git_tree_close(tree); - - must_be_true(git_oid_cmp(&id_hiearar, &id3) == 0); - - // check data is correct - must_pass(git_tree_lookup(&tree, repo, &id_hiearar)); - must_be_true(2 == git_tree_entrycount(tree)); - git_tree_close(tree); - - close_temp_repo(repo); - -END_TEST - -BEGIN_SUITE(tree) - //ADD_TEST(print0); - ADD_TEST(read0); - ADD_TEST(read1); - //ADD_TEST(write0); - //ADD_TEST(write1); - ADD_TEST(write2); - ADD_TEST(write3); -END_SUITE - diff --git a/tests/t10-refs.c b/tests/t10-refs.c deleted file mode 100644 index 4b34146ed..000000000 --- a/tests/t10-refs.c +++ /dev/null @@ -1,942 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "repository.h" - -static const char *loose_tag_ref_name = "refs/tags/e90810b"; -static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; - -BEGIN_TEST(readtag0, "lookup a loose tag reference") - git_repository *repo; - git_reference *reference; - git_object *object; - char ref_name_from_tag_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name)); - must_be_true(reference->type & GIT_REF_OID); - must_be_true((reference->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0); - - must_pass(git_object_lookup(&object, repo, git_reference_oid(reference), GIT_OBJ_ANY)); - must_be_true(object != NULL); - must_be_true(git_object_type(object) == GIT_OBJ_TAG); - - /* Ensure the name of the tag matches the name of the reference */ - git__joinpath(ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object)); - must_be_true(strcmp(ref_name_from_tag_name, loose_tag_ref_name) == 0); - - git_object_close(object); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(readtag1, "lookup a loose tag reference that doesn't exist") - git_repository *repo; - git_reference *reference; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_fail(git_reference_lookup(&reference, repo, non_existing_tag_ref_name)); - - git_repository_free(repo); -END_TEST - -static const char *head_tracker_sym_ref_name = "head-tracker"; -static const char *current_head_target = "refs/heads/master"; -static const char *current_master_tip = "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; - -BEGIN_TEST(readsym0, "lookup a symbolic reference") - git_repository *repo; - git_reference *reference, *resolved_ref; - git_object *object; - git_oid id; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE)); - must_be_true(reference->type & GIT_REF_SYMBOLIC); - must_be_true((reference->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(reference->name, GIT_HEAD_FILE) == 0); - - must_pass(git_reference_resolve(&resolved_ref, reference)); - must_be_true(resolved_ref->type == GIT_REF_OID); - - must_pass(git_object_lookup(&object, repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY)); - must_be_true(object != NULL); - must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); - - git_oid_mkstr(&id, current_master_tip); - must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); - - git_object_close(object); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(readsym1, "lookup a nested symbolic reference") - git_repository *repo; - git_reference *reference, *resolved_ref; - git_object *object; - git_oid id; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&reference, repo, head_tracker_sym_ref_name)); - must_be_true(reference->type & GIT_REF_SYMBOLIC); - must_be_true((reference->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(reference->name, head_tracker_sym_ref_name) == 0); - - must_pass(git_reference_resolve(&resolved_ref, reference)); - must_be_true(resolved_ref->type == GIT_REF_OID); - - must_pass(git_object_lookup(&object, repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY)); - must_be_true(object != NULL); - must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); - - git_oid_mkstr(&id, current_master_tip); - must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); - - git_object_close(object); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(readsym2, "lookup the HEAD and resolve the master branch") - git_repository *repo; - git_reference *reference, *resolved_ref, *comp_base_ref; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&reference, repo, head_tracker_sym_ref_name)); - must_pass(git_reference_resolve(&resolved_ref, reference)); - comp_base_ref = resolved_ref; - - must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE)); - must_pass(git_reference_resolve(&resolved_ref, reference)); - must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); - - must_pass(git_reference_lookup(&reference, repo, current_head_target)); - must_pass(git_reference_resolve(&resolved_ref, reference)); - must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref))); - - git_repository_free(repo); -END_TEST - -BEGIN_TEST(readsym3, "lookup the master branch and then the HEAD") - git_repository *repo; - git_reference *reference, *master_ref, *resolved_ref; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&master_ref, repo, current_head_target)); - must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE)); - - must_pass(git_reference_resolve(&resolved_ref, reference)); - must_pass(git_oid_cmp(git_reference_oid(master_ref), git_reference_oid(resolved_ref))); - - git_repository_free(repo); -END_TEST - -static const char *packed_head_name = "refs/heads/packed"; -static const char *packed_test_head_name = "refs/heads/packed-test"; - -BEGIN_TEST(readpacked0, "lookup a packed reference") - git_repository *repo; - git_reference *reference; - git_object *object; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&reference, repo, packed_head_name)); - must_be_true(reference->type & GIT_REF_OID); - must_be_true((reference->type & GIT_REF_PACKED) != 0); - must_be_true(strcmp(reference->name, packed_head_name) == 0); - - must_pass(git_object_lookup(&object, repo, git_reference_oid(reference), GIT_OBJ_ANY)); - must_be_true(object != NULL); - must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); - - git_object_close(object); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(readpacked1, "assure that a loose reference is looked up before a packed reference") - git_repository *repo; - git_reference *reference; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_pass(git_reference_lookup(&reference, repo, packed_head_name)); - must_pass(git_reference_lookup(&reference, repo, packed_test_head_name)); - must_be_true(reference->type & GIT_REF_OID); - must_be_true((reference->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(reference->name, packed_test_head_name) == 0); - - git_repository_free(repo); -END_TEST - -BEGIN_TEST(create0, "create a new symbolic reference") - git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_repository *repo, *repo2; - git_oid id; - char ref_path[GIT_PATH_MAX]; - - const char *new_head_tracker = "another-head-tracker"; - - git_oid_mkstr(&id, current_master_tip); - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Retrieve the physical path to the symbolic ref for further cleaning */ - git__joinpath(ref_path, repo->path_repository, new_head_tracker); - - /* Create and write the new symbolic reference */ - must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target)); - - /* Ensure the reference can be looked-up... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker)); - must_be_true(looked_up_ref->type & GIT_REF_SYMBOLIC); - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(looked_up_ref->name, new_head_tracker) == 0); - - /* ...peeled.. */ - must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - must_be_true(resolved_ref->type == GIT_REF_OID); - - /* ...and that it points to the current master tip */ - must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - - git_repository_free(repo); - - /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - - must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); - must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - - close_temp_repo(repo2); -END_TEST - -BEGIN_TEST(create1, "create a deep symbolic reference") - git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_repository *repo; - git_oid id; - char ref_path[GIT_PATH_MAX]; - - const char *new_head_tracker = "deep/rooted/tracker"; - - git_oid_mkstr(&id, current_master_tip); - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - git__joinpath(ref_path, repo->path_repository, new_head_tracker); - must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target)); - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker)); - must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(create2, "create a new OID reference") - git_reference *new_reference, *looked_up_ref; - git_repository *repo, *repo2; - git_oid id; - char ref_path[GIT_PATH_MAX]; - - const char *new_head = "refs/heads/new-head"; - - git_oid_mkstr(&id, current_master_tip); - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Retrieve the physical path to the symbolic ref for further cleaning */ - git__joinpath(ref_path, repo->path_repository, new_head); - - /* Create and write the new object id reference */ - must_pass(git_reference_create_oid(&new_reference, repo, new_head, &id)); - - /* Ensure the reference can be looked-up... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head)); - must_be_true(looked_up_ref->type & GIT_REF_OID); - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(looked_up_ref->name, new_head) == 0); - - /* ...and that it points to the current master tip */ - must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); - - git_repository_free(repo); - - /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - - must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); - must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); - - close_temp_repo(repo2); -END_TEST - -BEGIN_TEST(create3, "Can not create a new OID reference which targets at an unknown id") - git_reference *new_reference, *looked_up_ref; - git_repository *repo; - git_oid id; - - const char *new_head = "refs/heads/new-head"; - - git_oid_mkstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - /* Create and write the new object id reference */ - must_fail(git_reference_create_oid(&new_reference, repo, new_head, &id)); - - /* Ensure the reference can't be looked-up... */ - must_fail(git_reference_lookup(&looked_up_ref, repo, new_head)); - - git_repository_free(repo); -END_TEST - -static const char *ref_name = "refs/heads/other"; -static const char *ref_master_name = "refs/heads/master"; -static const char *ref_branch_name = "refs/heads/branch"; -static const char *ref_test_name = "refs/heads/test"; -BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference") - git_reference *ref, *branch_ref; - git_repository *repo; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* The target needds to exist and we need to check the name has changed */ - must_pass(git_reference_create_symbolic(&branch_ref, repo, ref_branch_name, ref_master_name)); - must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_branch_name)); - /* Ensure it points to the right place*/ - must_pass(git_reference_lookup(&ref, repo, ref_name)); - must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); - must_be_true(!strcmp(git_reference_target(ref), ref_branch_name)); - - /* Ensure we can't create it unless we force it to */ - must_fail(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name)); - must_pass(git_reference_create_symbolic_f(&ref, repo, ref_name, ref_master_name)); - - /* Ensure it points to the right place */ - must_pass(git_reference_lookup(&ref, repo, ref_name)); - must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); - must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") - git_reference *ref; - git_repository *repo; - git_oid id; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&ref, repo, ref_master_name)); - must_be_true(ref->type & GIT_REF_OID); - git_oid_cpy(&id, git_reference_oid(ref)); - - /* Create it */ - must_pass(git_reference_create_oid(&ref, repo, ref_name, &id)); - - must_pass(git_reference_lookup(&ref, repo, ref_test_name)); - must_be_true(ref->type & GIT_REF_OID); - git_oid_cpy(&id, git_reference_oid(ref)); - - /* Ensure we can't overwrite unless we force it */ - must_fail(git_reference_create_oid(&ref, repo, ref_name, &id)); - must_pass(git_reference_create_oid_f(&ref, repo, ref_name, &id)); - - /* Ensure it has been overwritten */ - must_pass(git_reference_lookup(&ref, repo, ref_name)); - must_be_true(!git_oid_cmp(&id, git_reference_oid(ref))); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symbolic one") - git_reference *ref; - git_repository *repo; - git_oid id; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&ref, repo, ref_master_name)); - must_be_true(ref->type & GIT_REF_OID); - git_oid_cpy(&id, git_reference_oid(ref)); - - must_pass(git_reference_create_oid(&ref, repo, ref_name, &id)); - must_fail(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name)); - must_pass(git_reference_create_symbolic_f(&ref, repo, ref_name, ref_master_name)); - - /* Ensure it points to the right place */ - must_pass(git_reference_lookup(&ref, repo, ref_name)); - must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); - must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object id one") - git_reference *ref; - git_repository *repo; - git_oid id; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - must_pass(git_reference_lookup(&ref, repo, ref_master_name)); - must_be_true(ref->type & GIT_REF_OID); - git_oid_cpy(&id, git_reference_oid(ref)); - - /* Create the symbolic ref */ - must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name)); - /* It shouldn't overwrite unless we tell it to */ - must_fail(git_reference_create_oid(&ref, repo, ref_name, &id)); - must_pass(git_reference_create_oid_f(&ref, repo, ref_name, &id)); - - /* Ensure it points to the right place */ - must_pass(git_reference_lookup(&ref, repo, ref_name)); - must_be_true(git_reference_type(ref) & GIT_REF_OID); - must_be_true(!git_oid_cmp(git_reference_oid(ref), &id)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(pack0, "create a packfile for an empty folder") - git_repository *repo; - char temp_path[GIT_PATH_MAX]; - const int mode = 0755; /* or 0777 ? */ - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - git__joinpath_n(temp_path, 3, repo->path_repository, GIT_REFS_HEADS_DIR, "empty_dir"); - must_pass(gitfo_mkdir_recurs(temp_path, mode)); - - must_pass(git_reference_packall(repo)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(pack1, "create a packfile from all the loose rn a repo") - git_repository *repo; - git_reference *reference; - char temp_path[GIT_PATH_MAX]; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Ensure a known loose ref can be looked up */ - must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name)); - must_be_true((reference->type & GIT_REF_PACKED) == 0); - must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0); - - /* - * We are now trying to pack also a loose reference - * called `points_to_blob`, to make sure we can properly - * pack weak tags - */ - must_pass(git_reference_packall(repo)); - - /* Ensure the packed-refs file exists */ - git__joinpath(temp_path, repo->path_repository, GIT_PACKEDREFS_FILE); - must_pass(gitfo_exists(temp_path)); - - /* Ensure the known ref can still be looked up but is now packed */ - must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name)); - must_be_true((reference->type & GIT_REF_PACKED) != 0); - must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0); - - /* Ensure the known ref has been removed from the loose folder structure */ - git__joinpath(temp_path, repo->path_repository, loose_tag_ref_name); - must_pass(!gitfo_exists(temp_path)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename0, "rename a loose reference") - git_reference *looked_up_ref, *another_looked_up_ref; - git_repository *repo; - char temp_path[GIT_PATH_MAX]; - const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu"; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Ensure the ref doesn't exist on the file system */ - git__joinpath(temp_path, repo->path_repository, new_name); - must_pass(!gitfo_exists(temp_path)); - - /* Retrieval of the reference to rename */ - must_pass(git_reference_lookup(&looked_up_ref, repo, loose_tag_ref_name)); - - /* ... which is indeed loose */ - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* Now that the reference is renamed... */ - must_pass(git_reference_rename(looked_up_ref, new_name)); - must_be_true(!strcmp(looked_up_ref->name, new_name)); - - /* ...It can't be looked-up with the old name... */ - must_fail(git_reference_lookup(&another_looked_up_ref, repo, loose_tag_ref_name)); - - /* ...but the new name works ok... */ - must_pass(git_reference_lookup(&another_looked_up_ref, repo, new_name)); - must_be_true(!strcmp(another_looked_up_ref->name, new_name)); - - /* .. the ref is still loose... */ - must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0); - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* ...and the ref can be found in the file system */ - git__joinpath(temp_path, repo->path_repository, new_name); - must_pass(gitfo_exists(temp_path)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename1, "rename a packed reference (should make it loose)") - git_reference *looked_up_ref, *another_looked_up_ref; - git_repository *repo; - char temp_path[GIT_PATH_MAX]; - const char *brand_new_name = "refs/heads/brand_new_name"; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Ensure the ref doesn't exist on the file system */ - git__joinpath(temp_path, repo->path_repository, packed_head_name); - must_pass(!gitfo_exists(temp_path)); - - /* The reference can however be looked-up... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name)); - - /* .. and it's packed */ - must_be_true((looked_up_ref->type & GIT_REF_PACKED) != 0); - - /* Now that the reference is renamed... */ - must_pass(git_reference_rename(looked_up_ref, brand_new_name)); - must_be_true(!strcmp(looked_up_ref->name, brand_new_name)); - - /* ...It can't be looked-up with the old name... */ - must_fail(git_reference_lookup(&another_looked_up_ref, repo, packed_head_name)); - - /* ...but the new name works ok... */ - must_pass(git_reference_lookup(&another_looked_up_ref, repo, brand_new_name)); - must_be_true(!strcmp(another_looked_up_ref->name, brand_new_name)); - - /* .. the ref is no longer packed... */ - must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0); - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* ...and the ref now happily lives in the file system */ - git__joinpath(temp_path, repo->path_repository, brand_new_name); - must_pass(gitfo_exists(temp_path)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename2, "renaming a packed reference does not pack another reference which happens to be in both loose and pack state") - git_reference *looked_up_ref, *another_looked_up_ref; - git_repository *repo; - char temp_path[GIT_PATH_MAX]; - const char *brand_new_name = "refs/heads/brand_new_name"; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Ensure the other reference exists on the file system */ - git__joinpath(temp_path, repo->path_repository, packed_test_head_name); - must_pass(gitfo_exists(temp_path)); - - /* Lookup the other reference */ - must_pass(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name)); - - /* Ensure it's loose */ - must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* Lookup the reference to rename */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name)); - - /* Ensure it's packed */ - must_be_true((looked_up_ref->type & GIT_REF_PACKED) != 0); - - /* Now that the reference is renamed... */ - must_pass(git_reference_rename(looked_up_ref, brand_new_name)); - - /* Lookup the other reference */ - must_pass(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name)); - - /* Ensure it's loose */ - must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* Ensure the other ref still exists on the file system */ - must_pass(gitfo_exists(temp_path)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename3, "can not rename a reference with the name of an existing reference") - git_reference *looked_up_ref; - git_repository *repo; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* An existing reference... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name)); - - /* Can not be renamed to the name of another existing reference. */ - must_fail(git_reference_rename(looked_up_ref, packed_test_head_name)); - - /* Failure to rename it hasn't corrupted its state */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name)); - must_be_true(!strcmp(looked_up_ref->name, packed_head_name)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename4, "can not rename a reference with an invalid name") - git_reference *looked_up_ref; - git_repository *repo; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* An existing oid reference... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name)); - - /* Can not be renamed with an invalid name. */ - must_fail(git_reference_rename(looked_up_ref, "Hello! I'm a very invalid name.")); - - /* Can not be renamed outside of the refs hierarchy. */ - must_fail(git_reference_rename(looked_up_ref, "i-will-sudo-you")); - - /* Failure to rename it hasn't corrupted its state */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name)); - must_be_true(!strcmp(looked_up_ref->name, packed_test_head_name)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(rename5, "can force-rename a reference with the name of an existing reference") - git_reference *looked_up_ref; - git_repository *repo; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* An existing reference... */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name)); - - /* Can be force-renamed to the name of another existing reference. */ - must_pass(git_reference_rename_f(looked_up_ref, packed_test_head_name)); - - /* Check we actually renamed it */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name)); - must_be_true(!strcmp(looked_up_ref->name, packed_test_head_name)); - - close_temp_repo(repo); -END_TEST - -BEGIN_TEST(delete0, "deleting a ref which is both packed and loose should remove both tracks in the filesystem") - git_reference *looked_up_ref, *another_looked_up_ref; - git_repository *repo; - char temp_path[GIT_PATH_MAX]; - - must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); - - /* Ensure the loose reference exists on the file system */ - git__joinpath(temp_path, repo->path_repository, packed_test_head_name); - must_pass(gitfo_exists(temp_path)); - - /* Lookup the reference */ - must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name)); - - /* Ensure it's the loose version that has been found */ - must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0); - - /* Now that the reference is deleted... */ - must_pass(git_reference_delete(looked_up_ref)); - - /* Looking up the reference once again should not retrieve it */ - must_fail(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name)); - - /* Ensure the loose reference doesn't exist any longer on the file system */ - must_pass(!gitfo_exists(temp_path)); - - close_temp_repo(repo); -END_TEST - -static int ensure_refname_normalized(int is_oid_ref, const char *input_refname, const char *expected_refname) -{ - int error = GIT_SUCCESS; - char buffer_out[GIT_PATH_MAX]; - - if (is_oid_ref) - error = git_reference__normalize_name_oid(buffer_out, input_refname); - else - error = git_reference__normalize_name(buffer_out, input_refname); - - if (error < GIT_SUCCESS) - return error; - - if (expected_refname == NULL) - return error; - - if (strcmp(buffer_out, expected_refname)) - error = GIT_ERROR; - - return error; -} - -#define OID_REF 1 -#define SYM_REF 0 - -BEGIN_TEST(normalize0, "normalize a direct (OID) reference name") - must_fail(ensure_refname_normalized(OID_REF, "a", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a/", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a.", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a.lock", NULL)); - must_pass(ensure_refname_normalized(OID_REF, "refs/dummy/a", NULL)); - must_pass(ensure_refname_normalized(OID_REF, "refs/stash", NULL)); - must_pass(ensure_refname_normalized(OID_REF, "refs/tags/a", "refs/tags/a")); - must_pass(ensure_refname_normalized(OID_REF, "refs/heads/a/b", "refs/heads/a/b")); - must_pass(ensure_refname_normalized(OID_REF, "refs/heads/a./b", "refs/heads/a./b")); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo?bar", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads\foo", NULL)); - must_pass(ensure_refname_normalized(OID_REF, "refs/heads/v@ation", "refs/heads/v@ation")); - must_pass(ensure_refname_normalized(OID_REF, "refs///heads///a", "refs/heads/a")); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/.a/b", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo/../bar", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo..bar", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/./foo", NULL)); - must_fail(ensure_refname_normalized(OID_REF, "refs/heads/v@{ation", NULL)); -END_TEST - -BEGIN_TEST(normalize1, "normalize a symbolic reference name") - must_pass(ensure_refname_normalized(SYM_REF, "a", "a")); - must_pass(ensure_refname_normalized(SYM_REF, "a/b", "a/b")); - must_pass(ensure_refname_normalized(SYM_REF, "refs///heads///a", "refs/heads/a")); - must_fail(ensure_refname_normalized(SYM_REF, "", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "heads\foo", NULL)); -END_TEST - -/* Ported from JGit, BSD licence. - * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 */ -BEGIN_TEST(normalize2, "tests borrowed from JGit") - -/* EmptyString */ - must_fail(ensure_refname_normalized(SYM_REF, "", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "/", NULL)); - -/* MustHaveTwoComponents */ - must_fail(ensure_refname_normalized(OID_REF, "master", NULL)); - must_pass(ensure_refname_normalized(SYM_REF, "heads/master", "heads/master")); - -/* ValidHead */ - - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/master", "refs/heads/master")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/pu", "refs/heads/pu")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/z", "refs/heads/z")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/FoO", "refs/heads/FoO")); - -/* ValidTag */ - must_pass(ensure_refname_normalized(SYM_REF, "refs/tags/v1.0", "refs/tags/v1.0")); - -/* NoLockSuffix */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master.lock", NULL)); - -/* NoDirectorySuffix */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master/", NULL)); - -/* NoSpace */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/i haz space", NULL)); - -/* NoAsciiControlCharacters */ - { - char c; - char buffer[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - for (c = '\1'; c < ' '; c++) { - strncpy(buffer, "refs/heads/mast", 15); - strncpy(buffer + 15, (const char *)&c, 1); - strncpy(buffer + 16, "er", 2); - buffer[18 - 1] = '\0'; - must_fail(ensure_refname_normalized(SYM_REF, buffer, NULL)); - } - } - -/* NoBareDot */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/.", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/..", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/./master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/../master", NULL)); - -/* NoLeadingOrTrailingDot */ - must_fail(ensure_refname_normalized(SYM_REF, ".", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/.bar", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/..bar", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/bar.", NULL)); - -/* ContainsDot */ - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r")); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master..pu", NULL)); - -/* NoMagicRefCharacters */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master^", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/^master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "^refs/heads/master", NULL)); - - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master~", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/~master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "~refs/heads/master", NULL)); - - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master:", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/:master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, ":refs/heads/master", NULL)); - -/* ShellGlob */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master?", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/?master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "?refs/heads/master", NULL)); - - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master[", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/[master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "[refs/heads/master", NULL)); - - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master*", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/*master", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "*refs/heads/master", NULL)); - -/* ValidSpecialCharacters */ - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/!", "refs/heads/!")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/\"", "refs/heads/\"")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/#", "refs/heads/#")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/$", "refs/heads/$")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/%", "refs/heads/%")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/&", "refs/heads/&")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/'", "refs/heads/'")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/(", "refs/heads/(")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/)", "refs/heads/)")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/+", "refs/heads/+")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/,", "refs/heads/,")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/-", "refs/heads/-")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/;", "refs/heads/;")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/<", "refs/heads/<")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/=", "refs/heads/=")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/>", "refs/heads/>")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/@", "refs/heads/@")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/]", "refs/heads/]")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/_", "refs/heads/_")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/`", "refs/heads/`")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/{", "refs/heads/{")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/|", "refs/heads/|")); - must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/}", "refs/heads/}")); - - // This is valid on UNIX, but not on Windows - // hence we make in invalid due to non-portability - // - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/\\", NULL)); - -/* UnicodeNames */ - /* - * Currently this fails. - * must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m")); - */ - -/* RefLogQueryIsValidRef */ - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master@{1}", NULL)); - must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master@{1.hour.ago}", NULL)); -END_TEST - -BEGIN_TEST(list0, "try to list all the references in our test repo") - git_repository *repo; - git_strarray ref_list; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_pass(git_reference_listall(&ref_list, repo, GIT_REF_LISTALL)); - - /*{ - unsigned short i; - for (i = 0; i < ref_list.count; ++i) - printf("# %s\n", ref_list.strings[i]); - }*/ - - /* We have exactly 8 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 */ - must_be_true(ref_list.count == 8); - - git_strarray_free(&ref_list); - git_repository_free(repo); -END_TEST - -BEGIN_TEST(list1, "try to list only the symbolic references") - git_repository *repo; - git_strarray ref_list; - - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - must_pass(git_reference_listall(&ref_list, repo, GIT_REF_SYMBOLIC)); - must_be_true(ref_list.count == 0); /* no symrefs in the test repo */ - - git_strarray_free(&ref_list); - git_repository_free(repo); -END_TEST - - -BEGIN_SUITE(refs) - ADD_TEST(readtag0); - ADD_TEST(readtag1); - - ADD_TEST(readsym0); - ADD_TEST(readsym1); - ADD_TEST(readsym2); - ADD_TEST(readsym3); - - ADD_TEST(readpacked0); - ADD_TEST(readpacked1); - - ADD_TEST(create0); - ADD_TEST(create1); - ADD_TEST(create2); - ADD_TEST(create3); - - ADD_TEST(overwrite0); - ADD_TEST(overwrite1); - ADD_TEST(overwrite2); - ADD_TEST(overwrite3); - - ADD_TEST(normalize0); - ADD_TEST(normalize1); - ADD_TEST(normalize2); - - ADD_TEST(pack0); - ADD_TEST(pack1); - - ADD_TEST(rename0); - ADD_TEST(rename1); - ADD_TEST(rename2); - ADD_TEST(rename3); - ADD_TEST(rename4); - ADD_TEST(rename5); - - ADD_TEST(delete0); - ADD_TEST(list0); - ADD_TEST(list1); -END_SUITE diff --git a/tests/t11-sqlite.c b/tests/t11-sqlite.c deleted file mode 100644 index 61ecf98ac..000000000 --- a/tests/t11-sqlite.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "odb.h" - -#ifdef GIT2_SQLITE_BACKEND -#include "t03-data.h" -#include "fileops.h" -#include "git2/odb_backend.h" - - -static int cmp_objects(git_odb_object *odb_obj, git_rawobj *raw) -{ - if (raw->type != git_odb_object_type(odb_obj)) - return -1; - - if (raw->len != git_odb_object_size(odb_obj)) - return -1; - - if ((raw->len > 0) && (memcmp(raw->data, git_odb_object_data(odb_obj), raw->len) != 0)) - return -1; - - return 0; -} - -static git_odb *open_sqlite_odb(void) -{ - git_odb *odb; - git_odb_backend *sqlite; - - if (git_odb_new(&odb) < GIT_SUCCESS) - return NULL; - - if (git_odb_backend_sqlite(&sqlite, ":memory") < GIT_SUCCESS) - return NULL; - - if (git_odb_add_backend(odb, sqlite, 0) < GIT_SUCCESS) - return NULL; - - return odb; -} - -#define TEST_WRITE(PTR) {\ - git_odb *db; \ - git_oid id1, id2; \ - git_odb_object *obj; \ - db = open_sqlite_odb(); \ - must_be_true(db != NULL); \ - must_pass(git_oid_mkstr(&id1, PTR.id)); \ - must_pass(git_odb_write(&id2, db, PTR##_obj.data, PTR##_obj.len, PTR##_obj.type)); \ - must_be_true(git_oid_cmp(&id1, &id2) == 0); \ - must_pass(git_odb_read(&obj, db, &id1)); \ - must_pass(cmp_objects(obj, &PTR##_obj)); \ - git_odb_object_close(obj); \ - git_odb_close(db); \ -} - -BEGIN_TEST(sqlite0, "write a commit, read it back (sqlite backend)") - TEST_WRITE(commit); -END_TEST - -BEGIN_TEST(sqlite1, "write a tree, read it back (sqlite backend)") - TEST_WRITE(tree); -END_TEST - -BEGIN_TEST(sqlite2, "write a tag, read it back (sqlite backend)") - TEST_WRITE(tag); -END_TEST - -BEGIN_TEST(sqlite3, "write a zero-byte entry, read it back (sqlite backend)") - TEST_WRITE(zero); -END_TEST - -BEGIN_TEST(sqlite4, "write a one-byte entry, read it back (sqlite backend)") - TEST_WRITE(one); -END_TEST - -BEGIN_TEST(sqlite5, "write a two-byte entry, read it back (sqlite backend)") - TEST_WRITE(two); -END_TEST - -BEGIN_TEST(sqlite6, "write some bytes in an entry, read it back (sqlite backend)") - TEST_WRITE(some); -END_TEST - - -BEGIN_SUITE(sqlite) - ADD_TEST(sqlite0); - ADD_TEST(sqlite1); - ADD_TEST(sqlite2); - ADD_TEST(sqlite3); - ADD_TEST(sqlite4); - ADD_TEST(sqlite5); - ADD_TEST(sqlite6); -END_SUITE - -#else /* no sqlite builtin */ -BEGIN_SUITE(sqlite) - /* empty */ -END_SUITE -#endif - - - diff --git a/tests/t12-repo.c b/tests/t12-repo.c deleted file mode 100644 index 4e51f1b15..000000000 --- a/tests/t12-repo.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include "odb.h" -#include "git2/odb_backend.h" -#include "repository.h" - -typedef struct { - git_odb_backend base; - int position; -} fake_backend; - -git_odb_backend *new_backend(int position) -{ - fake_backend *b; - - b = git__malloc(sizeof(fake_backend)); - if (b == NULL) - return NULL; - - memset(b, 0x0, sizeof(fake_backend)); - b->position = position; - return (git_odb_backend *)b; -} - -int test_backend_sorting(git_odb *odb) -{ - unsigned int i; - - for (i = 0; i < odb->backends.length; ++i) { - fake_backend *internal = *((fake_backend **)git_vector_get(&odb->backends, i)); - - if (internal == NULL) - return GIT_ERROR; - - if (internal->position != (int)i) - return GIT_ERROR; - } - - return GIT_SUCCESS; -} - -BEGIN_TEST(odb0, "assure that ODB backends are properly sorted") - git_odb *odb; - must_pass(git_odb_new(&odb)); - must_pass(git_odb_add_backend(odb, new_backend(0), 5)); - must_pass(git_odb_add_backend(odb, new_backend(2), 3)); - must_pass(git_odb_add_backend(odb, new_backend(1), 4)); - must_pass(git_odb_add_backend(odb, new_backend(3), 1)); - must_pass(test_backend_sorting(odb)); - git_odb_close(odb); -END_TEST - -BEGIN_TEST(odb1, "assure that alternate backends are properly sorted") - git_odb *odb; - must_pass(git_odb_new(&odb)); - must_pass(git_odb_add_backend(odb, new_backend(0), 5)); - must_pass(git_odb_add_backend(odb, new_backend(2), 3)); - must_pass(git_odb_add_backend(odb, new_backend(1), 4)); - must_pass(git_odb_add_backend(odb, new_backend(3), 1)); - must_pass(git_odb_add_alternate(odb, new_backend(4), 5)); - must_pass(git_odb_add_alternate(odb, new_backend(6), 3)); - must_pass(git_odb_add_alternate(odb, new_backend(5), 4)); - must_pass(git_odb_add_alternate(odb, new_backend(7), 1)); - must_pass(test_backend_sorting(odb)); - git_odb_close(odb); -END_TEST - - -#define STANDARD_REPOSITORY 0 -#define BARE_REPOSITORY 1 - -static int ensure_repository_init( - const char *working_directory, - int repository_kind, - const char *expected_path_index, - const char *expected_path_repository, - const char *expected_working_directory) -{ - char path_odb[GIT_PATH_MAX]; - git_repository *repo; - - if (gitfo_isdir(working_directory) == GIT_SUCCESS) - return GIT_ERROR; - - git__joinpath(path_odb, expected_path_repository, GIT_OBJECTS_DIR); - - if (git_repository_init(&repo, working_directory, repository_kind) < GIT_SUCCESS) - return GIT_ERROR; - - if (repo->path_workdir != NULL || expected_working_directory != NULL) { - if (git__suffixcmp(repo->path_workdir, expected_working_directory) != 0) - goto cleanup; - } - - if (git__suffixcmp(repo->path_odb, path_odb) != 0) - goto cleanup; - - if (git__suffixcmp(repo->path_repository, expected_path_repository) != 0) - goto cleanup; - - if (repo->path_index != NULL || expected_path_index != NULL) { - if (git__suffixcmp(repo->path_index, expected_path_index) != 0) - goto cleanup; - - if (git_repository_is_bare(repo) == 1) - goto cleanup; - } else if (git_repository_is_bare(repo) == 0) - goto cleanup; - - if (git_repository_is_empty(repo) == 0) - goto cleanup; - - git_repository_free(repo); - rmdir_recurs(working_directory); - - return GIT_SUCCESS; - -cleanup: - git_repository_free(repo); - rmdir_recurs(working_directory); - return GIT_ERROR; -} - -BEGIN_TEST(init0, "initialize a standard repo") - char path_index[GIT_PATH_MAX], path_repository[GIT_PATH_MAX]; - - git__joinpath(path_repository, TEMP_REPO_FOLDER, GIT_DIR); - git__joinpath(path_index, path_repository, GIT_INDEX_FILE); - - must_pass(ensure_repository_init(TEMP_REPO_FOLDER, STANDARD_REPOSITORY, path_index, path_repository, TEMP_REPO_FOLDER)); - must_pass(ensure_repository_init(TEMP_REPO_FOLDER_NS, STANDARD_REPOSITORY, path_index, path_repository, TEMP_REPO_FOLDER)); -END_TEST - -BEGIN_TEST(init1, "initialize a bare repo") - char path_repository[GIT_PATH_MAX]; - - git__joinpath(path_repository, TEMP_REPO_FOLDER, ""); - - must_pass(ensure_repository_init(TEMP_REPO_FOLDER, BARE_REPOSITORY, NULL, path_repository, NULL)); - must_pass(ensure_repository_init(TEMP_REPO_FOLDER_NS, BARE_REPOSITORY, NULL, path_repository, NULL)); -END_TEST - -BEGIN_TEST(init2, "Initialize and open a bare repo with a relative path escaping out of the current working directory") - char path_repository[GIT_PATH_MAX]; - char current_workdir[GIT_PATH_MAX]; - const int mode = 0755; /* or 0777 ? */ - git_repository* repo; - - must_pass(gitfo_getcwd(current_workdir, sizeof(current_workdir))); - - git__joinpath(path_repository, TEMP_REPO_FOLDER, "a/b/c/"); - must_pass(gitfo_mkdir_recurs(path_repository, mode)); - - must_pass(chdir(path_repository)); - - must_pass(git_repository_init(&repo, "../d/e.git", 1)); - must_pass(git__suffixcmp(repo->path_repository, "/a/b/d/e.git/")); - - git_repository_free(repo); - - must_pass(git_repository_open(&repo, "../d/e.git")); - - git_repository_free(repo); - - must_pass(chdir(current_workdir)); - rmdir_recurs(TEMP_REPO_FOLDER); -END_TEST - -#define EMPTY_BARE_REPOSITORY_NAME "empty_bare.git" -#define EMPTY_BARE_REPOSITORY_FOLDER TEST_RESOURCES "/" EMPTY_BARE_REPOSITORY_NAME "/" - -BEGIN_TEST(open0, "Open a bare repository that has just been initialized by git") - git_repository *repo; - - must_pass(copydir_recurs(EMPTY_BARE_REPOSITORY_FOLDER, TEMP_REPO_FOLDER)); - must_pass(remove_placeholders(TEMP_REPO_FOLDER, "dummy-marker.txt")); - - must_pass(git_repository_open(&repo, TEMP_REPO_FOLDER)); - must_be_true(git_repository_path(repo) != NULL); - must_be_true(git_repository_workdir(repo) == NULL); - - git_repository_free(repo); - must_pass(rmdir_recurs(TEMP_REPO_FOLDER)); -END_TEST - -#define SOURCE_EMPTY_REPOSITORY_NAME "empty_standard_repo/.gitted" -#define EMPTY_REPOSITORY_NAME "empty_standard_repo/.git" -#define EMPTY_REPOSITORY_FOLDER TEST_RESOURCES "/" SOURCE_EMPTY_REPOSITORY_NAME "/" -#define DEST_REPOSITORY_FOLDER TEMP_REPO_FOLDER DOT_GIT "/" - -BEGIN_TEST(open1, "Open a standard repository that has just been initialized by git") - git_repository *repo; - - must_pass(copydir_recurs(EMPTY_REPOSITORY_FOLDER, DEST_REPOSITORY_FOLDER)); - must_pass(remove_placeholders(DEST_REPOSITORY_FOLDER, "dummy-marker.txt")); - - must_pass(git_repository_open(&repo, DEST_REPOSITORY_FOLDER)); - must_be_true(git_repository_path(repo) != NULL); - must_be_true(git_repository_workdir(repo) != NULL); - - git_repository_free(repo); - must_pass(rmdir_recurs(TEMP_REPO_FOLDER)); -END_TEST - - -BEGIN_TEST(open2, "Open a bare repository with a relative path escaping out of the current working directory") - char new_current_workdir[GIT_PATH_MAX]; - char current_workdir[GIT_PATH_MAX]; - char path_repository[GIT_PATH_MAX]; - - const int mode = 0755; /* or 0777 ? */ - git_repository* repo; - - /* Setup the repository to open */ - must_pass(gitfo_getcwd(current_workdir, sizeof(current_workdir))); - strcpy(path_repository, current_workdir); - git__joinpath_n(path_repository, 3, path_repository, TEMP_REPO_FOLDER, "a/d/e.git"); - must_pass(copydir_recurs(REPOSITORY_FOLDER, path_repository)); - - /* Change the current working directory */ - git__joinpath(new_current_workdir, TEMP_REPO_FOLDER, "a/b/c/"); - must_pass(gitfo_mkdir_recurs(new_current_workdir, mode)); - must_pass(chdir(new_current_workdir)); - - must_pass(git_repository_open(&repo, "../../d/e.git")); - - git_repository_free(repo); - - must_pass(chdir(current_workdir)); - rmdir_recurs(TEMP_REPO_FOLDER); -END_TEST - -BEGIN_TEST(empty0, "test if a repository is empty or not") - - git_repository *repo_empty, *repo_normal; - - must_pass(git_repository_open(&repo_normal, REPOSITORY_FOLDER)); - must_be_true(git_repository_is_empty(repo_normal) == 0); - git_repository_free(repo_normal); - - must_pass(git_repository_open(&repo_empty, EMPTY_BARE_REPOSITORY_FOLDER)); - must_be_true(git_repository_is_empty(repo_empty) == 1); - git_repository_free(repo_empty); -END_TEST - -BEGIN_SUITE(repository) - ADD_TEST(odb0); - ADD_TEST(odb1); - ADD_TEST(init0); - ADD_TEST(init1); - ADD_TEST(init2); - ADD_TEST(open0); - ADD_TEST(open1); - ADD_TEST(open2); - ADD_TEST(empty0); -END_SUITE - diff --git a/tests/t13-threads.c b/tests/t13-threads.c deleted file mode 100644 index 3888b70ce..000000000 --- a/tests/t13-threads.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" -#include "cache.h" - - -typedef struct { - git_cached_obj cached; - unsigned int __dummy; -} ttest_obj; - -BEGIN_TEST(cache0, "run several threads polling the cache at the same time") - -END_TEST - -BEGIN_SUITE(threads) - ADD_TEST(cache0); -END_SUITE diff --git a/tests/t14-hiredis.c b/tests/t14-hiredis.c deleted file mode 100644 index c743f7d48..000000000 --- a/tests/t14-hiredis.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "odb.h" - -#ifdef GIT2_HIREDIS_BACKEND -#include "t03-data.h" -#include "fileops.h" -#include "git2/odb_backend.h" - - -static int cmp_objects(git_odb_object *odb_obj, git_rawobj *raw) -{ - if (raw->type != git_odb_object_type(odb_obj)) - return -1; - - if (raw->len != git_odb_object_size(odb_obj)) - return -1; - - if ((raw->len > 0) && (memcmp(raw->data, git_odb_object_data(odb_obj), raw->len) != 0)) - return -1; - - return 0; -} - -static git_odb *open_hiredis_odb(void) -{ - git_odb *odb; - git_odb_backend *hiredis; - - if (git_odb_new(&odb) < GIT_SUCCESS) - return NULL; - - if (git_odb_backend_hiredis(&hiredis, "127.0.0.1", 6379) < GIT_SUCCESS) - return NULL; - - if (git_odb_add_backend(odb, hiredis, 0) < GIT_SUCCESS) - return NULL; - - return odb; -} - -#define TEST_WRITE(PTR) {\ - git_odb *db; \ - git_oid id1, id2; \ - git_odb_object *obj; \ - db = open_hiredis_odb(); \ - must_be_true(db != NULL); \ - must_pass(git_oid_mkstr(&id1, PTR.id)); \ - must_pass(git_odb_write(&id2, db, PTR##_obj.data, PTR##_obj.len, PTR##_obj.type)); \ - must_be_true(git_oid_cmp(&id1, &id2) == 0); \ - must_pass(git_odb_read(&obj, db, &id1)); \ - must_pass(cmp_objects(obj, &PTR##_obj)); \ - git_odb_object_close(obj); \ - git_odb_close(db); \ -} - -BEGIN_TEST(hiredis0, "write a commit, read it back (hiredis backend)") - TEST_WRITE(commit); -END_TEST - -BEGIN_TEST(hiredis1, "write a tree, read it back (hiredis backend)") - TEST_WRITE(tree); -END_TEST - -BEGIN_TEST(hiredis2, "write a tag, read it back (hiredis backend)") - TEST_WRITE(tag); -END_TEST - -BEGIN_TEST(hiredis3, "write a zero-byte entry, read it back (hiredis backend)") - TEST_WRITE(zero); -END_TEST - -BEGIN_TEST(hiredis4, "write a one-byte entry, read it back (hiredis backend)") - TEST_WRITE(one); -END_TEST - -BEGIN_TEST(hiredis5, "write a two-byte entry, read it back (hiredis backend)") - TEST_WRITE(two); -END_TEST - -BEGIN_TEST(hiredis6, "write some bytes in an entry, read it back (hiredis backend)") - TEST_WRITE(some); -END_TEST - - -BEGIN_SUITE(hiredis) - ADD_TEST(hiredis0); - ADD_TEST(hiredis1); - ADD_TEST(hiredis2); - ADD_TEST(hiredis3); - ADD_TEST(hiredis4); - ADD_TEST(hiredis5); - ADD_TEST(hiredis6); -END_SUITE - -#else /* no hiredis builtin */ -BEGIN_SUITE(hiredis) - /* empty */ -END_SUITE -#endif diff --git a/tests/t15-config.c b/tests/t15-config.c deleted file mode 100644 index 08a2cdbf2..000000000 --- a/tests/t15-config.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#include "test_lib.h" -#include "test_helpers.h" - -#include - -#define CONFIG_BASE TEST_RESOURCES "/config" - -/* - * This one is so we know the code isn't completely broken - */ -BEGIN_TEST(config0, "read a simple configuration") - git_config *cfg; - int i; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config0")); - must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i)); - must_be_true(i == 0); - must_pass(git_config_get_bool(cfg, "core.filemode", &i)); - must_be_true(i == 1); - must_pass(git_config_get_bool(cfg, "core.bare", &i)); - must_be_true(i == 0); - must_pass(git_config_get_bool(cfg, "core.logallrefupdates", &i)); - must_be_true(i == 1); - - git_config_free(cfg); -END_TEST - -/* - * [this "that"] and [this "That] are different namespaces. Make sure - * each returns the correct one. - */ -BEGIN_TEST(config1, "case sensitivity") - git_config *cfg; - int i; - const char *str; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config1")); - - must_pass(git_config_get_string(cfg, "this.that.other", &str)); - must_be_true(!strcmp(str, "true")); - must_pass(git_config_get_string(cfg, "this.That.other", &str)); - must_be_true(!strcmp(str, "yes")); - - must_pass(git_config_get_bool(cfg, "this.that.other", &i)); - must_be_true(i == 1); - must_pass(git_config_get_bool(cfg, "this.That.other", &i)); - must_be_true(i == 1); - - /* This one doesn't exist */ - must_fail(git_config_get_bool(cfg, "this.thaT.other", &i)); - - git_config_free(cfg); -END_TEST - -/* - * If \ is the last non-space character on the line, we read the next - * one, separating each line with SP. - */ -BEGIN_TEST(config2, "parse a multiline value") - git_config *cfg; - const char *str; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config2")); - - must_pass(git_config_get_string(cfg, "this.That.and", &str)); - must_be_true(!strcmp(str, "one one one two two three three")); - - git_config_free(cfg); -END_TEST - -/* - * This kind of subsection declaration is case-insensitive - */ -BEGIN_TEST(config3, "parse a [section.subsection] header") - git_config *cfg; - const char *str; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config3")); - - must_pass(git_config_get_string(cfg, "section.subsection.var", &str)); - must_be_true(!strcmp(str, "hello")); - - /* Avoid a false positive */ - str = "nohello"; - must_pass(git_config_get_string(cfg, "section.subSectIon.var", &str)); - must_be_true(!strcmp(str, "hello")); - - git_config_free(cfg); -END_TEST - -BEGIN_TEST(config4, "a variable name on its own is valid") - git_config *cfg; -const char *str; -int i; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config4")); - - must_pass(git_config_get_string(cfg, "some.section.variable", &str)); - must_be_true(str == NULL); - - must_pass(git_config_get_bool(cfg, "some.section.variable", &i)); - must_be_true(i == 1); - - - git_config_free(cfg); -END_TEST - -BEGIN_TEST(config5, "test number suffixes") - git_config *cfg; - long int i; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config5")); - - must_pass(git_config_get_long(cfg, "number.simple", &i)); - must_be_true(i == 1); - - must_pass(git_config_get_long(cfg, "number.k", &i)); - must_be_true(i == 1 * 1024); - - must_pass(git_config_get_long(cfg, "number.kk", &i)); - must_be_true(i == 1 * 1024); - - must_pass(git_config_get_long(cfg, "number.m", &i)); - must_be_true(i == 1 * 1024 * 1024); - - must_pass(git_config_get_long(cfg, "number.mm", &i)); - must_be_true(i == 1 * 1024 * 1024); - - must_pass(git_config_get_long(cfg, "number.g", &i)); - must_be_true(i == 1 * 1024 * 1024 * 1024); - - must_pass(git_config_get_long(cfg, "number.gg", &i)); - must_be_true(i == 1 * 1024 * 1024 * 1024); - - git_config_free(cfg); -END_TEST - -BEGIN_TEST(config6, "test blank lines") - git_config *cfg; - int i; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config6")); - - must_pass(git_config_get_bool(cfg, "valid.subsection.something", &i)); - must_be_true(i == 1); - - must_pass(git_config_get_bool(cfg, "something.else.something", &i)); - must_be_true(i == 0); - - git_config_free(cfg); -END_TEST - -BEGIN_TEST(config7, "test for invalid ext headers") - git_config *cfg; - - must_fail(git_config_open_file(&cfg, CONFIG_BASE "/config7")); - -END_TEST - -BEGIN_TEST(config8, "don't fail on empty files") - git_config *cfg; - - must_pass(git_config_open_file(&cfg, CONFIG_BASE "/config8")); - - git_config_free(cfg); -END_TEST - -BEGIN_SUITE(config) - ADD_TEST(config0); - ADD_TEST(config1); - ADD_TEST(config2); - ADD_TEST(config3); - ADD_TEST(config4); - ADD_TEST(config5); - ADD_TEST(config6); - ADD_TEST(config7); - ADD_TEST(config8); -END_SUITE diff --git a/tests/test_helpers.c b/tests/test_helpers.c deleted file mode 100644 index 760de238b..000000000 --- a/tests/test_helpers.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "test_helpers.h" -#include "fileops.h" - -int write_object_data(char *file, void *data, size_t len) -{ - git_file fd; - int ret; - - if ((fd = gitfo_creat(file, S_IREAD | S_IWRITE)) < 0) - return -1; - ret = gitfo_write(fd, data, len); - gitfo_close(fd); - - return ret; -} - -int write_object_files(const char *odb_dir, object_data *d) -{ - if (gitfo_mkdir(odb_dir, 0755) < 0) { - int err = errno; - fprintf(stderr, "can't make directory \"%s\"", odb_dir); - if (err == EEXIST) - fprintf(stderr, " (already exists)"); - fprintf(stderr, "\n"); - return -1; - } - - if ((gitfo_mkdir(d->dir, 0755) < 0) && (errno != EEXIST)) { - fprintf(stderr, "can't make object directory \"%s\"\n", d->dir); - return -1; - } - if (write_object_data(d->file, d->bytes, d->blen) < 0) { - fprintf(stderr, "can't write object file \"%s\"\n", d->file); - return -1; - } - - return 0; -} - -int remove_object_files(const char *odb_dir, object_data *d) -{ - if (gitfo_unlink(d->file) < 0) { - fprintf(stderr, "can't delete object file \"%s\"\n", d->file); - return -1; - } - if ((gitfo_rmdir(d->dir) < 0) && (errno != ENOTEMPTY)) { - fprintf(stderr, "can't remove object directory \"%s\"\n", d->dir); - return -1; - } - - if (gitfo_rmdir(odb_dir) < 0) { - fprintf(stderr, "can't remove directory \"%s\"\n", odb_dir); - return -1; - } - - return 0; -} - -int remove_loose_object(const char *repository_folder, git_object *object) -{ - static const char *objects_folder = "objects/"; - - char *ptr, *full_path, *top_folder; - int path_length, objects_length; - - assert(repository_folder && object); - - objects_length = strlen(objects_folder); - path_length = strlen(repository_folder); - ptr = full_path = git__malloc(path_length + objects_length + GIT_OID_HEXSZ + 3); - - strcpy(ptr, repository_folder); - strcpy(ptr + path_length, objects_folder); - - ptr = top_folder = ptr + path_length + objects_length; - *ptr++ = '/'; - git_oid_pathfmt(ptr, git_object_id(object)); - ptr += GIT_OID_HEXSZ + 1; - *ptr = 0; - - if (gitfo_unlink(full_path) < 0) { - fprintf(stderr, "can't delete object file \"%s\"\n", full_path); - return -1; - } - - *top_folder = 0; - - if ((gitfo_rmdir(full_path) < 0) && (errno != ENOTEMPTY)) { - fprintf(stderr, "can't remove object directory \"%s\"\n", full_path); - return -1; - } - - free(full_path); - - return GIT_SUCCESS; -} - -int cmp_objects(git_rawobj *o, object_data *d) -{ - if (o->type != git_object_string2type(d->type)) - return -1; - if (o->len != d->dlen) - return -1; - if ((o->len > 0) && (memcmp(o->data, d->data, o->len) != 0)) - return -1; - return 0; -} - -int copy_file(const char *src, const char *dst) -{ - gitfo_buf source_buf; - git_file dst_fd; - int error = GIT_ERROR; - - if (gitfo_read_file(&source_buf, src) < GIT_SUCCESS) - return GIT_ENOTFOUND; - - dst_fd = gitfo_creat_force(dst, 0644); - if (dst_fd < 0) - goto cleanup; - - error = gitfo_write(dst_fd, source_buf.data, source_buf.len); - -cleanup: - gitfo_free_buf(&source_buf); - gitfo_close(dst_fd); - - return error; -} - -int cmp_files(const char *a, const char *b) -{ - gitfo_buf buf_a, buf_b; - int error = GIT_ERROR; - - if (gitfo_read_file(&buf_a, a) < GIT_SUCCESS) - return GIT_ERROR; - - if (gitfo_read_file(&buf_b, b) < GIT_SUCCESS) { - gitfo_free_buf(&buf_a); - return GIT_ERROR; - } - - if (buf_a.len == buf_b.len && !memcmp(buf_a.data, buf_b.data, buf_a.len)) - error = GIT_SUCCESS; - - gitfo_free_buf(&buf_a); - gitfo_free_buf(&buf_b); - - return error; -} - -static int remove_filesystem_element_recurs(void *GIT_UNUSED(nil), char *path) -{ - int error = GIT_SUCCESS; - - GIT_UNUSED_ARG(nil); - - error = gitfo_isdir(path); - if (error == GIT_SUCCESS) { - size_t root_size = strlen(path); - - error = gitfo_dirent(path, GIT_PATH_MAX, remove_filesystem_element_recurs, NULL); - if (error < GIT_SUCCESS) - return error; - - path[root_size] = 0; - return rmdir(path); - } - - return gitfo_unlink(path); -} - -int rmdir_recurs(const char *directory_path) -{ - char buffer[GIT_PATH_MAX]; - strcpy(buffer, directory_path); - return remove_filesystem_element_recurs(NULL, buffer); -} - -typedef struct { - size_t src_len, dst_len; - char *dst; -} copydir_data; - -static int copy_filesystem_element_recurs(void *_data, char *source) -{ - copydir_data *data = (copydir_data *)_data; - - data->dst[data->dst_len] = 0; - git__joinpath(data->dst, data->dst, source + data->src_len); - - if (gitfo_isdir(source) == GIT_SUCCESS) - return gitfo_dirent(source, GIT_PATH_MAX, copy_filesystem_element_recurs, _data); - - return copy_file(source, data->dst); -} - -int copydir_recurs(const char *source_directory_path, const char *destination_directory_path) -{ - char source_buffer[GIT_PATH_MAX]; - char dest_buffer[GIT_PATH_MAX]; - copydir_data data; - - /* Source has to exist, Destination hast to _not_ exist */ - if (gitfo_isdir(source_directory_path) || !gitfo_isdir(destination_directory_path)) - return GIT_EINVALIDPATH; - - git__joinpath(source_buffer, source_directory_path, ""); - data.src_len = strlen(source_buffer); - - git__joinpath(dest_buffer, destination_directory_path, ""); - data.dst = dest_buffer; - data.dst_len = strlen(dest_buffer); - - return copy_filesystem_element_recurs(&data, source_buffer); -} - -int open_temp_repo(git_repository **repo, const char *path) -{ - int error; - if ((error = copydir_recurs(path, TEMP_REPO_FOLDER)) < GIT_SUCCESS) - return error; - - return git_repository_open(repo, TEMP_REPO_FOLDER); -} - -void close_temp_repo(git_repository *repo) -{ - git_repository_free(repo); - rmdir_recurs(TEMP_REPO_FOLDER); -} - -static int remove_placeholders_recurs(void *filename, char *path) -{ - char passed_filename[GIT_PATH_MAX]; - char *data = (char *)filename; - - if (!gitfo_isdir(path)) - return gitfo_dirent(path, GIT_PATH_MAX, remove_placeholders_recurs, data); - - if (git__basename_r(passed_filename, sizeof(passed_filename), path) < GIT_SUCCESS) - return GIT_EINVALIDPATH; - - if (!strcmp(data, passed_filename)) - return gitfo_unlink(path); - - return GIT_SUCCESS; -} - -int remove_placeholders(char *directory_path, char *filename) -{ - char buffer[GIT_PATH_MAX]; - - if (gitfo_isdir(directory_path)) - return GIT_EINVALIDPATH; - - strcpy(buffer, directory_path); - return remove_placeholders_recurs(filename, buffer); -} diff --git a/tests/test_helpers.h b/tests/test_helpers.h deleted file mode 100644 index 19c8ae55c..000000000 --- a/tests/test_helpers.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef INCLUDE_test_helpers_h__ -#define INCLUDE_test_helpers_h__ - -#include "test_lib.h" -#include - -#include "odb.h" - -#define TEST_REPOSITORY_NAME "testrepo.git" -#define REPOSITORY_FOLDER TEST_RESOURCES "/" TEST_REPOSITORY_NAME "/" -#define ODB_FOLDER (REPOSITORY_FOLDER "objects/") -#define TEST_INDEX_PATH (REPOSITORY_FOLDER "index") -#define TEST_INDEX2_PATH (TEST_RESOURCES "/gitgit.index") -#define TEST_INDEXBIG_PATH (TEST_RESOURCES "/big.index") - -#define TEMP_FOLDER "" -#define TEMP_REPO_FOLDER TEMP_FOLDER TEST_REPOSITORY_NAME "/" -#define TEMP_REPO_FOLDER_NS TEMP_FOLDER TEST_REPOSITORY_NAME - -typedef struct object_data { - unsigned char *bytes; /* (compressed) bytes stored in object store */ - size_t blen; /* length of data in object store */ - char *id; /* object id (sha1) */ - char *type; /* object type */ - char *dir; /* object store (fan-out) directory name */ - char *file; /* object store filename */ - unsigned char *data; /* (uncompressed) object data */ - size_t dlen; /* length of (uncompressed) object data */ -} object_data; - -extern int write_object_data(char *file, void *data, size_t len); - -extern int write_object_files(const char *odb_dir, object_data *d); - -extern int remove_object_files(const char *odb_dir, object_data *d); - -extern int cmp_objects(git_rawobj *o, object_data *d); - -extern int remove_loose_object(const char *odb_dir, git_object *object); - -extern int cmp_files(const char *a, const char *b); -extern int copy_file(const char *source, const char *dest); -extern int rmdir_recurs(const char *directory_path); -extern int copydir_recurs(const char *source_directory_path, const char *destination_directory_path); -extern int remove_placeholders(char *directory_path, char *filename); - -extern int open_temp_repo(git_repository **repo, const char *path); -extern void close_temp_repo(git_repository *repo); - -#endif -/* INCLUDE_test_helpers_h__ */ diff --git a/tests/test_lib.c b/tests/test_lib.c deleted file mode 100755 index aaacdff65..000000000 --- a/tests/test_lib.c +++ /dev/null @@ -1,202 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "test_lib.h" - -#define DO_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE))) -#define GIT_MAX_TEST_CASES 64 - -struct git_test { - char *name; - char *message; - char *failed_pos; - char *description; - - int ret_value; - - git_testfunc function; - unsigned failed:1, ran:1; - jmp_buf *jump; -}; - -struct git_testsuite { - char *name; - int count, fail_count; - git_test *list[GIT_MAX_TEST_CASES]; -}; - -static void test_free(git_test *t) -{ - if (t) { - free(t->name); - free(t->description); - free(t->failed_pos); - free(t->message); - free(t); - } -} - -static void test_run(git_test *tc) -{ - jmp_buf buf; - tc->jump = &buf; - - if (setjmp(buf) == 0) { - tc->ran = 1; - (tc->function)(tc); - } - - tc->jump = 0; -} - -static git_test *create_test(git_testfunc function) -{ - git_test *t = DO_ALLOC(git_test); - - t->name = NULL; - t->failed = 0; - t->ran = 0; - t->description = NULL; - t->message = NULL; - t->ret_value = 0; - t->failed_pos = NULL; - t->function = function; - t->jump = NULL; - - return t; -} - -void git_test__init(git_test *t, const char *name, const char *description) -{ - t->name = strdup(name); - t->description = strdup(description); -} - - -/*-------------------------------------------------------------------------* - * Public assert methods - *-------------------------------------------------------------------------*/ - -static void fail_test(git_test *tc, const char *file, int line, const char *message, int ret_value) -{ - char buf[1024]; - - snprintf(buf, 1024, "%s:%d", file, line); - - tc->failed = 1; - tc->message = strdup(message); - tc->ret_value = ret_value; - tc->failed_pos = strdup(buf); - - if (tc->jump != 0) - longjmp(*(tc->jump), 0); -} - -void git_test__fail(git_test *tc, const char *file, int line, const char *message) -{ - fail_test(tc, file, line, message, 0); -} - -void git_test__assert(git_test *tc, const char *file, int line, const char *message, int condition) -{ - if (condition == 0) - fail_test(tc, file, line, message, 0); -} - -void git_test__assert_pass(git_test *tc, const char *file, int line, const char *message, int ret_value) -{ - if (ret_value < 0) - fail_test(tc, file, line, message, ret_value); -} - -/*-------------------------------------------------------------------------* - * Test Suite - *-------------------------------------------------------------------------*/ - -static void testsuite_init(git_testsuite *ts) -{ - ts->count = 0; - ts->fail_count = 0; - memset(ts->list, 0, sizeof(ts->list)); -} - -git_testsuite *git_testsuite_new(const char *name) -{ - git_testsuite *ts = DO_ALLOC(git_testsuite); - testsuite_init(ts); - ts->name = strdup(name); - return ts; -} - -static void free_suite(git_testsuite *ts) -{ - unsigned int n; - - for (n = 0; n < GIT_MAX_TEST_CASES; n++) - if (ts->list[n]) - test_free(ts->list[n]); - - free(ts->name); - free(ts); -} - -void git_testsuite_add(git_testsuite *ts, git_testfunc test) -{ - assert(ts->count < GIT_MAX_TEST_CASES); - ts->list[ts->count++] = create_test(test); -} - -static void print_details(git_testsuite *ts) -{ - int i; - int failCount = 0; - - if (ts->fail_count == 0) { - const char *testWord = ts->count == 1 ? "test" : "tests"; - printf("OK (%d %s)\n", ts->count, testWord); - } else { - printf("Failed (%d failures):\n", ts->fail_count); - - for (i = 0 ; i < ts->count ; ++i) { - git_test *tc = ts->list[i]; - if (tc->failed) { - failCount++; - printf(" %d) \"%s\" [test %s @ %s]\n\t%s\n", - failCount, tc->description, tc->name, tc->failed_pos, tc->message); - if (tc->ret_value) - printf("\tError: (%d) %s\n", tc->ret_value, git_lasterror()); - } - } - } -} - -int git_testsuite_run(git_testsuite *ts) -{ - int i, fail_count; - - printf("Suite \"%s\": ", ts->name); - - for (i = 0 ; i < ts->count ; ++i) { - git_test *tc = ts->list[i]; - - test_run(tc); - if (tc->failed) { - ts->fail_count++; - putchar('F'); - } else - putchar('.'); - - fflush(stdout); - } - printf("\n "); - print_details(ts); - fail_count = ts->fail_count; - - free_suite(ts); - return fail_count; -} - diff --git a/tests/test_lib.h b/tests/test_lib.h deleted file mode 100755 index f3febe857..000000000 --- a/tests/test_lib.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef __LIBGIT2_TEST_H__ -#define __LIBGIT2_TEST_H__ - -#include -#include -#include -#include - -#include "common.h" -#include - -#define DECLARE_SUITE(SNAME) extern git_testsuite *libgit2_suite_##SNAME(void) -#define SUITE_NAME(SNAME) libgit2_suite_##SNAME - -#define BEGIN_SUITE(SNAME) \ - git_testsuite *libgit2_suite_##SNAME(void) {\ - git_testsuite *_gitsuite = git_testsuite_new(#SNAME); - -#define ADD_TEST(TNAME) \ - git_testsuite_add(_gitsuite, _gittest__##TNAME); - -#define END_SUITE \ - return _gitsuite;\ - } - -#define BEGIN_TEST(TNAME, DESC) \ - static void _gittest__##TNAME(git_test *_gittest) { \ - git_test__init(_gittest, #TNAME, DESC); \ - {\ - -#define END_TEST }} - -typedef struct git_test git_test; -typedef struct git_testsuite git_testsuite; -typedef void (*git_testfunc)(git_test *); -typedef git_testsuite *(*libgit2_suite)(void); - -void git_test__init(git_test *t, const char *name, const char *description); -void git_test__fail(git_test *tc, const char *file, int line, const char *message); -void git_test__assert(git_test *tc, const char *file, int line, const char *message, int condition); -void git_test__assert_pass(git_test *tc, const char *file, int line, const char *message, int ret_value); - -#define must_pass(expr) git_test__assert_pass(_gittest, __FILE__, __LINE__, "Method failed: " #expr, (expr)) -#define must_fail(expr) git_test__assert(_gittest, __FILE__, __LINE__, "Expected method to fail: " #expr, (expr) < 0) -#define must_be_true(expr) git_test__assert(_gittest, __FILE__, __LINE__, "Expression is not true: " #expr, !!(expr)) - -git_testsuite *git_testsuite_new(const char *name); -void git_testsuite_add(git_testsuite *ts, git_testfunc test); -int git_testsuite_run(git_testsuite *ts); - -#endif - diff --git a/tests/test_main.c b/tests/test_main.c deleted file mode 100644 index 0f5f16a26..000000000 --- a/tests/test_main.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include - -#include "test_lib.h" -#include "test_helpers.h" - -DECLARE_SUITE(core); -DECLARE_SUITE(rawobjects); -DECLARE_SUITE(objread); -DECLARE_SUITE(objwrite); -DECLARE_SUITE(commit); -DECLARE_SUITE(revwalk); -DECLARE_SUITE(index); -DECLARE_SUITE(hashtable); -DECLARE_SUITE(tag); -DECLARE_SUITE(tree); -DECLARE_SUITE(refs); -DECLARE_SUITE(sqlite); -DECLARE_SUITE(hiredis); -DECLARE_SUITE(repository); -DECLARE_SUITE(threads); -DECLARE_SUITE(config); - -static libgit2_suite suite_methods[]= { - SUITE_NAME(core), - SUITE_NAME(rawobjects), - SUITE_NAME(objread), - SUITE_NAME(objwrite), - SUITE_NAME(commit), - SUITE_NAME(revwalk), - SUITE_NAME(index), - SUITE_NAME(hashtable), - SUITE_NAME(tag), - SUITE_NAME(tree), - SUITE_NAME(refs), - SUITE_NAME(sqlite), - SUITE_NAME(repository), - SUITE_NAME(threads), - SUITE_NAME(hiredis), - SUITE_NAME(config), -}; - -#define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods)) - -int main(int GIT_UNUSED(argc), char *GIT_UNUSED(argv[])) -{ - unsigned int i, failures; - - GIT_UNUSED_ARG(argc); - GIT_UNUSED_ARG(argv); - - failures = 0; - - for (i = 0; i < GIT_SUITE_COUNT; ++i) - failures += git_testsuite_run(suite_methods[i]()); - - return failures ? -1 : 0; -} - diff --git a/tests/tests.supp b/tests/tests.supp deleted file mode 100644 index fe9d965dc..000000000 --- a/tests/tests.supp +++ /dev/null @@ -1,6 +0,0 @@ -{ - ignore-zlib-cond - Memcheck:Cond - obj:*libz.so* -} - diff --git a/waf b/waf deleted file mode 100755 index 18b9aa3e4..000000000 Binary files a/waf and /dev/null differ diff --git a/wscript b/wscript deleted file mode 100644 index fd877e468..000000000 --- a/wscript +++ /dev/null @@ -1,284 +0,0 @@ -from __future__ import with_statement -from waflib.Context import Context -from waflib.Build import BuildContext, CleanContext, \ - InstallContext, UninstallContext - -# Unix flags -CFLAGS_UNIX = ["-O2", "-Wall", "-Wextra", "-fPIC"] -CFLAGS_UNIX_DBG = ['-g', '-O0'] - -# Windows MSVC flags -CFLAGS_WIN32_COMMON = ['/TC', '/W4', '/WX', '/nologo', '/Zi'] -CFLAGS_WIN32_RELEASE = ['/O2', '/MD'] - -# Note: /RTC* cannot be used with optimization on. -CFLAGS_WIN32_DBG = ['/Od', '/RTC1', '/RTCc', '/DEBUG', '/MDd'] -CFLAGS_WIN32_L = ['/RELEASE'] # used for /both/ debug and release builds. - # sets the module's checksum in the header. -CFLAGS_WIN32_L_DBG = ['/DEBUG'] - -ALL_LIBS = ['crypto', 'pthread', 'sqlite3', 'hiredis'] - -def options(opt): - opt.load('compiler_c') - opt.add_option('--sha1', action='store', default='builtin', - help="Use the builtin SHA1 routines (builtin), the \ -PPC optimized version (ppc) or the SHA1 functions from OpenSSL (openssl)") - opt.add_option('--debug', action='store_true', default=False, - help='Compile with debug symbols') - opt.add_option('--msvc', action='store', default=None, - help='Force a specific MSVC++ version (7.1, 8.0, 9.0, 10.0), if more than one is installed') - opt.add_option('--arch', action='store', default='x86', - help='Select target architecture (ia64, x64, x86, x86_amd64, x86_ia64)') - opt.add_option('--with-sqlite', action='store_true', default=False, - dest='use_sqlite', help='Enable sqlite support') - opt.add_option('--with-hiredis', action='store_true', default=False, - dest='use_hiredis', help='Enable redis support using hiredis') - opt.add_option('--threadsafe', action='store_true', default=False, - help='Make libgit2 thread-safe (requires pthreads)') - -def configure(conf): - - # load the MSVC configuration flags - if conf.options.msvc: - conf.env['MSVC_VERSIONS'] = ['msvc ' + conf.options.msvc] - - conf.env['MSVC_TARGETS'] = [conf.options.arch] - - # default configuration for C programs - conf.load('compiler_c') - - dbg = conf.options.debug - - conf.env.CFLAGS += CFLAGS_UNIX + (CFLAGS_UNIX_DBG if dbg else []) - - if conf.env.DEST_OS == 'win32': - conf.env.PLATFORM = 'win32' - - if conf.env.CC_NAME == 'msvc': - conf.env.CFLAGS = CFLAGS_WIN32_COMMON + \ - (CFLAGS_WIN32_DBG if dbg else CFLAGS_WIN32_RELEASE) - conf.env.LINKFLAGS += CFLAGS_WIN32_L + \ - (CFLAGS_WIN32_L_DBG if dbg else []) - conf.env.DEFINES += ['WIN32', '_DEBUG', '_LIB'] - - else: - conf.env.PLATFORM = 'unix' - - if conf.env.DEST_OS == 'sunos': - conf.env.DEFINES += ['NO_VIZ'] - - if conf.options.threadsafe: - if conf.env.PLATFORM == 'unix': - conf.check_cc(lib='pthread', uselib_store='pthread') - conf.env.DEFINES += ['GIT_THREADS'] - - # check for sqlite3 - if conf.options.use_sqlite and conf.check_cc( - lib='sqlite3', uselib_store='sqlite3', install_path=None, mandatory=False): - conf.env.DEFINES += ['GIT2_SQLITE_BACKEND'] - - # check for hiredis - if conf.options.use_hiredis and conf.check_cc( - lib='hiredis', uselib_store='hiredis', install_path=None, mandatory=False): - conf.env.DEFINES += ['GIT2_HIREDIS_BACKEND'] - - - if conf.options.sha1 not in ['openssl', 'ppc', 'builtin']: - conf.fatal('Invalid SHA1 option') - - # check for libcrypto (openssl) if we are using its SHA1 functions - if conf.options.sha1 == 'openssl': - conf.check_cfg(package='libcrypto', args=['--cflags', '--libs'], uselib_store='crypto') - conf.env.DEFINES += ['OPENSSL_SHA1'] - - elif conf.options.sha1 == 'ppc': - conf.env.DEFINES += ['PPC_SHA1'] - - conf.env.sha1 = conf.options.sha1 - -def build(bld): - - # command '[build|clean|install|uninstall]-static' - if bld.variant == 'static': - build_library(bld, 'static') - - # command '[build|clean|install|uninstall]-shared' - elif bld.variant == 'shared': - build_library(bld, 'shared') - - # command '[build|clean]-tests' - elif bld.variant == 'test': - build_library(bld, 'objects') - build_test(bld) - - # command 'build|clean|install|uninstall': by default, run - # the same command for both the static and the shared lib - else: - from waflib import Options - Options.commands = [bld.cmd + '-shared', bld.cmd + '-static'] + Options.commands - -def get_libgit2_version(git2_h): - import re - line = None - - with open(git2_h) as f: - line = re.search(r'^#define LIBGIT2_VERSION "(\d+\.\d+\.\d+)"$', f.read(), re.MULTILINE) - - if line is None: - raise Exception("Failed to detect libgit2 version") - - return line.group(1) - - -def build_library(bld, build_type): - - BUILD = { - 'shared' : bld.shlib, - 'static' : bld.stlib, - 'objects' : bld.objects - } - - directory = bld.path - sources = directory.ant_glob('src/*.c') - - # Find the version of the library, from our header file - version = get_libgit2_version(directory.find_node("include/git2.h").abspath()) - - # Compile platform-dependant code - # E.g. src/unix/*.c - # src/win32/*.c - sources = sources + directory.ant_glob('src/%s/*.c' % bld.env.PLATFORM) - sources = sources + directory.ant_glob('src/backends/*.c') - sources = sources + directory.ant_glob('deps/zlib/*.c') - - # SHA1 methods source - if bld.env.sha1 == "ppc": - sources.append('src/ppc/sha1.c') - else: - sources.append('src/block-sha1/sha1.c') - #------------------------------ - # Build the main library - #------------------------------ - - # either as static or shared; - BUILD[build_type]( - source=sources, - target='git2', - includes=['src', 'include', 'deps/zlib'], - install_path='${LIBDIR}', - use=ALL_LIBS, - vnum=version, - ) - - # On Unix systems, build the Pkg-config entry file - if bld.env.PLATFORM == 'unix' and bld.is_install: - bld(rule="""sed -e 's#@prefix@#${PREFIX}#' -e 's#@libdir@#${LIBDIR}#' -e 's#@version@#%s#' < ${SRC} > ${TGT}""" % version, - source='libgit2.pc.in', - target='libgit2.pc', - install_path='${LIBDIR}/pkgconfig', - ) - - # Install headers - bld.install_files('${PREFIX}/include', directory.find_node('include/git2.h')) - bld.install_files('${PREFIX}/include/git2', directory.ant_glob('include/git2/*.h')) - - # On Unix systems, let them know about installation - if bld.env.PLATFORM == 'unix' and bld.cmd == 'install-shared': - bld.add_post_fun(call_ldconfig) - -def call_ldconfig(bld): - import distutils.spawn as s - ldconf = s.find_executable('ldconfig') - if ldconf: - bld.exec_command(ldconf) - -def build_test(bld): - directory = bld.path - resources_path = directory.find_node('tests/resources/').abspath().replace('\\', '/') - - sources = ['tests/test_lib.c', 'tests/test_helpers.c', 'tests/test_main.c'] - sources = sources + directory.ant_glob('tests/t??-*.c') - - bld.program( - source=sources, - target='libgit2_test', - includes=['src', 'tests', 'include'], - defines=['TEST_RESOURCES="%s"' % resources_path], - use=['git2'] + ALL_LIBS - ) - -class _test(BuildContext): - cmd = 'test' - fun = 'test' - -def test(bld): - from waflib import Options - Options.commands = ['build-test', 'run-test'] + Options.commands - -class _build_doc(Context): - cmd = 'doxygen' - fun = 'build_docs' - -def build_docs(ctx): - ctx.exec_command("doxygen api.doxygen") - ctx.exec_command("git stash") - ctx.exec_command("git checkout gh-pages") - ctx.exec_command("cp -Rf apidocs/html/* .") - ctx.exec_command("git add .") - ctx.exec_command("git commit -am 'generated docs'") - ctx.exec_command("git push origin gh-pages") - ctx.exec_command("git checkout master") - -class _run_test(Context): - cmd = 'run-test' - fun = 'run_test' - -def run_test(ctx): - import shutil, tempfile, sys - - failed = False - - test_path = 'build/test/libgit2_test' - if sys.platform == 'win32': - test_path += '.exe' - - test_folder = tempfile.mkdtemp() - test = ctx.path.find_node(test_path) - - if not test or ctx.exec_command(test.abspath(), cwd=test_folder) != 0: - failed = True - - shutil.rmtree(test_folder) - - if failed: - ctx.fatal('Test run failed') - - -CONTEXTS = { - 'build' : BuildContext, - 'clean' : CleanContext, - 'install' : InstallContext, - 'uninstall' : UninstallContext -} - -def build_command(command): - ctx, var = command.split('-') - class _gen_command(CONTEXTS[ctx]): - cmd = command - variant = var - -build_command('build-static') -build_command('build-shared') -build_command('build-test') - -build_command('clean-static') -build_command('clean-shared') -build_command('clean-test') - -build_command('install-static') -build_command('install-shared') - -build_command('uninstall-static') -build_command('uninstall-shared') -